import pygame
from pygame.locals import *
from math import floor
from random import randint, choice, uniform, sample
from pgu import gui
import textwrap

from constants import *
from display import d
from town import make_item
from sounds import s
  
# gameplay classes and functions

def success_chance(x):
    if x>=12:
        return 100
    elif x==11:
        return 99
    elif x==10:
        return 97
    elif x==9:
        return 95
    elif x==8:
        return 90
    elif x==7:
        return 70
    elif x==6:
        return 50
    elif x==5:
        return 30
    elif x==4:
        return 10
    elif x==3:
        return 5
    elif x==2:
        return 3
    elif x==1:
        return 1
    else:
        return 0
    
def round_to_1_dp(x):
    return 0.1 * round(10 * x)

def round_to_2_dp(x):
    return 0.01 * round(100 * x)

def one_in(x):
    if x==0:
        return True
    else:
        return randint(1,x)==1

def adjacent(pos1,pos2):
    if pos1==POS_C_FRONT_1 and pos2==POS_C_FRONT_2:
            return True
    if pos1==POS_C_FRONT_3 and pos2==POS_C_FRONT_2:
            return True
    if pos1==POS_C_FRONT_2 and (pos2==POS_C_FRONT_1 or pos2==POS_C_FRONT_3):
            return True
    if pos1==POS_C_BACK_1 and pos2==POS_C_BACK_2:
            return True
    if pos1==POS_C_BACK_3 and pos2==POS_C_BACK_2:
            return True
    if pos1==POS_C_BACK_2 and (pos2==POS_C_BACK_1 or pos2==POS_C_BACK_3):
            return True
    if pos1==POS_M_FRONT_1 and pos2==POS_M_FRONT_2:
            return True
    if pos1==POS_M_FRONT_3 and pos2==POS_M_FRONT_2:
            return True
    if pos1==POS_M_FRONT_2 and (pos2==POS_M_FRONT_1 or pos2==POS_M_FRONT_3):
            return True
    if pos1==POS_M_BACK_1 and pos2==POS_M_BACK_2:
            return True
    if pos1==POS_M_BACK_3 and pos2==POS_M_BACK_2:
            return True
    if pos1==POS_M_BACK_2 and (pos2==POS_M_BACK_1 or pos2==POS_M_BACK_3):
            return True
    return False

def opposite_sides(pos1,pos2):
    if pos1==POS_C_FRONT_1 or pos1==POS_C_BACK_1 or pos1==POS_M_FRONT_1 or pos1==POS_M_BACK_1:
        if pos1==POS_C_FRONT_3 or pos1==POS_C_BACK_3 or pos1==POS_M_FRONT_3 or pos1==POS_M_BACK_3:
            return True
    if pos1==POS_C_FRONT_3 or pos1==POS_C_BACK_3 or pos1==POS_M_FRONT_3 or pos1==POS_M_BACK_3:
        if pos1==POS_C_FRONT_1 or pos1==POS_C_BACK_1 or pos1==POS_M_FRONT_1 or pos1==POS_M_BACK_1:
            return True
    return False

def reflex_chance(reflexes,level,action_reflex_level):
    result = 20 + (level + (reflexes-3) - action_reflex_level) * 15
    return max(5,min(result,success_chance(reflexes + 3)))  

def cost_mult(stamina):
    if stamina==1:
        result = 2
    elif stamina==2:
        result = 1.6
    elif stamina==3:
        result = 1.2
    elif stamina==4:
        result = 1
    elif stamina==5:
        result = 0.85
    elif stamina==6:
        result = 0.7
    elif stamina==7:
        result = 0.65
    return result

class Effect_parameters:
    def __init__(self, duration):
        self.lasts_until = duration + w.elapsed_since_beginning
        # may also have a member 'level' (e.g. for poison or sleep)
    
class Character_action:
    def __init__(self):
        self.last_used = None
        self.limited_uses = None

    def message(self):
        return ''

    def callable(self):
        return True

    def target_friend(self):
        return False
    
    def target_enemy(self):
        return False

    def enemy_target_back_row_ok(self):
        return True # but usually suppressed by target_enemy=False
    
    def enemy_target_front_row_ok(self):
        return True # but usually suppressed by target_enemy=False

    def reflex_level(self):
        return 1
    
    def delay(self):
        return 0.0

    def cooldown(self):
        return None

    def disruptable(self):
        return False

    def cost(self):
        if self.base_cost()>0:
            return int(max(1,floor(self.base_cost() * cost_mult(self.c.stamina))))
        else:
            return 0

    def base_cost(self):
        return 0
    
    def back_row_ok(self):
        return True

    def front_row_ok(self):
        return True

    def needed(self):
        return False # default is never carried out by reflex

    def reflex_target(self):
        return None
    
    def ok(self):
        if not self.callable():
            return False
        if self.c.front_row() and not self.front_row_ok():
            return False
        if not self.c.front_row() and not self.back_row_ok():
            return False
        if (self.target_enemy() or self.target_friend()) and len(self.valid_targets())==0:
            return False
        if self.cooldown() is not None and self.last_used is not None and self.last_used+self.cooldown()>w.elapsed_since_beginning:
            return False
        if self.c.energy<self.cost():
            return False
        if self.limited_uses==0:
            return False
        if self.c.current_stance==ST_BERSERK:
            return False
        if self.c.has_status_effect(EF_GRAPPLE):
            return False
        if self.disruptable() and self.c.has_status_effect(EF_FEAR):
            return False
        if w.silence_until > w.elapsed_since_beginning and self.disruptable():
            return False
        return True

    def enemy_target_ok(self,m):
        if m.front_row() and not self.enemy_target_front_row_ok():
            return False
        if not m.front_row() and not self.enemy_target_back_row_ok():
            return False
        if not m.alive:
            return False
        if m.about_to_die:
            return False
        return True

    def friend_target_ok(self,c):
        if c==self.c:
            return False
        if not c.alive:
            return False
        if c.about_to_die:
            return False
        return True

    def valid_targets(self):
        if self.target_enemy():
            targets = []
            # want to sort the options nicely by position
            for pos in [POS_M_FRONT_1,POS_M_FRONT_2,POS_M_FRONT_3,POS_M_BACK_1,POS_M_BACK_2,POS_M_BACK_3]:
                for m in e.mobs:
                    if m.pos==pos and self.enemy_target_ok(m):
                        targets.append(m)
            return targets
        elif self.target_friend():
            targets = []
            for c in p.chars:
                if self.friend_target_ok(c):
                    targets.append(c)
            return targets
        else:
            return None
        
    def trigger(self,target):
        self.c.current_stance = None
        self.last_used = w.elapsed_since_beginning
        if self.delay()>0:
            self.c.action_in_progress = self
            self.c.action_fires = w.elapsed_since_beginning + self.delay()
            self.c.target_of_action_in_progress = target
        else:
            self.resolve(target)

    def resolve(self,target):
        self.c.current_stance = None
        if (self.target_friend() and not self.friend_target_ok(target)) or (self.target_enemy() and not self.enemy_target_ok(target)) or (self.c.energy < self.cost()):
            d.message_queue.append(('Action failed',self.c.image,COL_LIGHT))
        else:
            self.c.energy = self.c.energy - self.cost()
            if self.limited_uses is not None:
                self.limited_uses = self.limited_uses - 1
            self.c.flash_image = I_VERYPALE
            if self.message()<>'':
                d.message_queue.append((self.message(),self.c.image,COL_LIGHT))
            if self.name()<>'':
                if target is not None:
                    d.log(self.c.name + ' uses ' + self.name() + ' on ' + target.name)
                else:
                    d.log(self.c.name + ' uses ' + self.name())
            self.make_stuff_happen(target)

    def make_stuff_happen(self,target):
        pass # stub

    def get_description(self):
        result = []
        result.append(self.name())
        result.append(self.describe_resolution())
        if self.delay() > 0:
            result.append(str(round_to_1_dp(self.delay()/TIME_SCALAR)) + ' s delay')
        if self.cooldown() is not None:
            result.append(str(round_to_1_dp(self.cooldown()/TIME_SCALAR)) + ' s cooldown')
        if self.disruptable():
            result.append('Disrupted if you take damage')
        if self.limited_uses is not None:
            result.append(str(self.limited_uses) + ' uses remaining')
        if self.cost()>0:
            result.append('Costs ' + str(self.cost()) + ' bar')
        if self.describe_reflex_condition() is not None:
            result.append('Can be used by reflex if ' + self.describe_reflex_condition() + ' (' + str(reflex_chance(self.c.reflexes,self.c.level,self.reflex_level())) + '% chance)')        
        return result

    def describe_resolution(self):
        return None # stub

    def describe_reflex_condition(self):
        return None # stub

class Potion_CA(Character_action):
    def __init__(self,n_potions):
        Character_action.__init__(self)
        self.limited_uses = n_potions

    def delay(self):
        return 2.0

    def reflex_level(self):
        return 2

class PotInvulnerability_CA(Potion_CA):
    def __init__(self,n_potions):
        Potion_CA.__init__(self,n_potions)

    def name(self):
        return 'Protection Potion'

    def make_stuff_happen(self,target):
        d.sound_queue.append(S_POTION)
        self.c.current_stance = ST_INVULNERABLE
        self.c.stance_wears_off = w.elapsed_since_beginning + self.duration()

    def delay(self):
        return 1.0

    def duration(self):
        return 5.0 

    def describe_resolution(self):
        return 'Makes the entire party immune from physical damage for ' + str(round_to_1_dp(self.duration()/TIME_SCALAR)) + ' seconds'

class PotBerserk_CA(Potion_CA):
    def __init__(self,n_potions):
        Potion_CA.__init__(self,n_potions)

    def name(self):
        return 'Potion of Rage'

    def make_stuff_happen(self,target):
        d.sound_queue.append(S_POTION)        
        self.c.current_stance = ST_BERSERK
        self.c.stance_wears_off = w.elapsed_since_beginning + 15

    def describe_resolution(self):
        return 'Gives you +30% base damage and +3 attack speed and health regeneration - but you cannot use any abilities and your energy bar does not regenerate normally. Lasts for ' + str(round_to_1_dp(15/TIME_SCALAR)) + ' seconds'

class PotCourage_CA(Potion_CA):
    def __init__(self,n_potions):
        Potion_CA.__init__(self,n_potions)

    def name(self):
        return 'Flask of Brandy'

    def needed(self):
        if self.c.alive and not self.c.about_to_die and self.c.has_status_effect(EF_FEAR):
            return True
        else:
            return False

    def make_stuff_happen(self,target):
        d.sound_queue.append(S_POTION)
        if self.c.has_status_effect(EF_FEAR):
            self.c.status_effects[EF_FEAR].lasts_until = 0
 
    def describe_resolution(self):
        return 'Drink this down if you are afflicted by fear, it will give you courage'

    def describe_reflex_condition(self):
        return 'you are afraid'

class PotFireresist_CA(Potion_CA):
    def __init__(self,n_potions):
        Potion_CA.__init__(self,n_potions)

    def name(self):
        return 'Potion of Resist Fire'

    def make_stuff_happen(self,target):
        d.sound_queue.append(S_POTION)
        self.c.status_effects[EF_RESIST_FIRE] = Effect_parameters(40)

    def needed(self):
        if self.c.has_status_effect(EF_RESIST_FIRE):
            return False
        for m in e.mobs:
            if m.alive and not (m.about_to_die) and m.action_in_progress is not None and m.action_in_progress.fiery():
                if m.action_fires <= w.elapsed_since_beginning + 0.5:
                    return True
        return False

    def describe_resolution(self):
        return 'Protect yourself from fire, halving damage for ' + str(round_to_1_dp(40/TIME_SCALAR)) + ' seconds'

    def describe_reflex_condition(self):
        return 'a monster is about to use a powerful fire attack against you'

class PotColdresist_CA(Potion_CA):
    def __init__(self,n_potions):
        Potion_CA.__init__(self,n_potions)

    def name(self):
        return 'Potion of Resist Cold'

    def make_stuff_happen(self,target):
        d.sound_queue.append(S_POTION)
        self.c.status_effects[EF_RESIST_COLD] = Effect_parameters(40)

    def needed(self):
        if self.c.has_status_effect(EF_RESIST_COLD):
            return False
        for m in e.mobs:
            if m.alive and not (m.about_to_die) and m.action_in_progress is not None and m.action_in_progress.freezy():
                if m.action_fires <= w.elapsed_since_beginning + 0.5:
                    return True
        return False

    def describe_resolution(self):
        return 'Protect yourself from cold, halving damage for ' + str(round_to_1_dp(40/TIME_SCALAR)) + ' seconds'

    def describe_reflex_condition(self):
        return 'a monster is about to use a powerful cold attack against you'
    
class PotAntidote_CA(Potion_CA):
    def __init__(self,n_potions):
        Potion_CA.__init__(self,n_potions)

    def name(self):
        return 'Antidote'

    def make_stuff_happen(self,target):
        d.sound_queue.append(S_POTION)
        if self.c.has_status_effect(EF_POISON):
            self.c.status_effects[EF_POISON].level = 0
            self.c.status_effects[EF_POISON].lasts_until = 0

    def needed(self):
        if self.c.alive and not self.c.about_to_die and self.c.has_status_effect(EF_POISON) and self.c.status_effects[EF_POISON].level>0:
            if w.poison_cloud_until is None:
                return True
            if w.poison_cloud_until < w.elapsed_since_beginning:
                return True
            if not self.c.front_row():
                return True
        return False

    def describe_resolution(self):
        return 'Cure poison'
    
    def describe_reflex_condition(self):
        return 'you are poisoned'

class PotMedheal_CA(Potion_CA):
    def __init__(self,n_potions):
        Potion_CA.__init__(self,n_potions)

    def name(self):
        return 'Health Potion'

    def trigger_level(self):
        return 0.3

    def amount(self):
        return 10

    def make_stuff_happen(self,target):
        d.sound_queue.append(S_POTION)
        old_health = self.c.health
        self.c.health = min(self.c.health+self.amount(),self.c.max_health)
        if self.c.health > old_health:
            self.c.flash_number = self.c.health - old_health
            self.c.number_starts = w.elapsed_since_beginning
            self.c.number_color = [0,255,0]
            d.log('Regains ' + str(self.c.flash_number) + ' health')

    def needed(self):
        if self.c.health < (self.trigger_level() * self.c.max_health):
            return True
        else:
            return False

    def describe_resolution(self):
        return 'Regain ' + str(self.amount()) + ' health'

    def describe_reflex_condition(self):
        return 'you have less than ' + str(int(100*self.trigger_level())) + '% health'

class PotPercentheal_CA(PotMedheal_CA):
    def __init__(self,n_potions):
        PotMedheal_CA.__init__(self,n_potions)

    def name(self):
        return 'Potion of Vitality'

    def trigger_level(self):
        return 0.3

    def amount(self):
        return int(floor(self.c.max_health*0.25))

class PotSmallheal_CA(PotMedheal_CA):
    def __init__(self,n_potions):
        PotMedheal_CA.__init__(self,n_potions)

    def name(self):
        return 'Minor Health Potion'

    def trigger_level(self):
        return 0.5

    def amount(self):
        return 6

class PotBigheal_CA(PotMedheal_CA):
    def __init__(self,n_potions):
        PotMedheal_CA.__init__(self,n_potions)

    def name(self):
        return 'Major Health Potion'

    def trigger_level(self):
        return 0.4

    def amount(self):
        return 15
    
class PotBarmage_CA(Potion_CA):
    def __init__(self,n_potions):
        Potion_CA.__init__(self,n_potions)

    def name(self):
        return 'Mana Potion'

    def trigger_level(self):
        return 0.3

    def amount(self):
        return 10
    
    def make_stuff_happen(self,target):
        d.sound_queue.append(S_POTION)
        regained = min(self.amount(), self.c.max_energy - self.c.energy)
        d.log('Regains ' + str(regained) + ' bar')
        self.c.energy = self.c.energy + regained

    def needed(self):
        if self.c.energy < (self.trigger_level() * self.c.max_energy):
            return True
        else:
            return False

    def describe_resolution(self):
        return 'Regain ' + str(self.amount()) + ' bar'

    def describe_reflex_condition(self):
        return 'you have less than ' + str(int(100*self.trigger_level())) + '% bar'

class PotBarrogue_CA(PotBarmage_CA):
    def __init__(self,n_potions):
        PotBarmage_CA.__init__(self,n_potions)

    def name(self):
        return 'Flask of Wine'

    def trigger_level(self):
        return 0.3

    def amount(self):
        return 10

class PotBarpriest_CA(PotBarmage_CA):
    def __init__(self,n_potions):
        PotBarmage_CA.__init__(self,n_potions)

    def name(self):
        return 'Blessed Potion'

    def trigger_level(self):
        return 0.3

    def amount(self):
        return 10
    
class Spell_CA(Character_action):
    def delay(self):
        return 2.0

    def message(self):
        return ''# 'Shazam!'

    def disruptable(self):
        return True
    
class Bash_CA(Character_action):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Bash'

    def cooldown(self):
        return 6.0

    def target_enemy(self):
        return True

    def enemy_target_back_row_ok(self):
        return False

    def back_row_ok(self):
        return False

    def base_cost(self):
        return 6

    def damage_mult(self):
        return 1.3

    def delay(self):
        return 0.4

    def char_is_aggressive(self):
        for action in self.c.actions:
            if action.action_code == CA_AGGRESSIVE:
                return True
        return False
    
    def make_stuff_happen(self,target):
        if self.c.melee_hit_roll(target):
            d.sound_queue.append(S_SMITE)
            #d.message_queue.append(('THUMP!',self.c.image,COL_LIGHT))
            target.take_damage(max(0,int(round(self.damage_mult() * self.c.melee_damage_roll(target)))-target.current_armor()))
        else:
            d.sound_queue.append(self.c.melee_miss_sound)
            d.message_queue.append(('Missed!',self.c.image,COL_LIGHT))

    def describe_resolution(self):
        return 'Attack a front row target of your choice for +30% damage'

    def needed(self):
        if self.char_is_aggressive():
            if self.c.energy < ((2 * self.c.max_energy) / 3):
                return False
            for m in e.mobs:
                if m.alive and (not m.about_to_die) and m.front_row():
                    if m.health >= 30:
                        return True
        return False

    def reflex_target(self):
        for m in e.mobs:
            if m.alive and (not m.about_to_die) and m.front_row():
                if m.health >= 30:
                    return m
        return False

    def describe_reflex_condition(self):
        if self.char_is_aggressive():
            return 'you have plenty of energy and there is an opponent with at least 30 health before you'
        else:
            return None

class Smite_CA(Bash_CA):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Smite'

    def base_cost(self):
        return 10

    def damage_mult(self):
        return 1.8

    def describe_resolution(self):
        return 'Attack a front row target of your choice for +80% damage'

class Deathblow_CA(Character_action):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Deathblow'

    def cooldown(self):
        return 3.0

    def target_enemy(self):
        return True

    def enemy_target_back_row_ok(self):
        return False

    def back_row_ok(self):
        return False

    def base_cost(self):
        return 6

    def damage_mult(self):
        return 1.3

    def make_stuff_happen(self,target):
        if self.c.melee_hit_roll(target):
            armor = target.current_armor()
            if self.c.has_status_effect(EF_FIND_WEAKNESS):
                armor = max(0,armor-5)
            if (target.health <= 0.3 * target.max_health) and (target.health <= 40) and (int(round(self.damage_mult() * self.c.melee_damage_roll(target))) > armor):
                d.sound_queue.append(self.c.melee_hit_sound)
                d.message_queue.append(('\"Die!!\"',self.c.image,COL_LIGHT))
                target.take_damage(999)
            else:
                d.sound_queue.append(self.c.melee_miss_sound)
                d.message_queue.append(('Failed!',self.c.image,COL_LIGHT))
        else:
            d.sound_queue.append(self.c.melee_miss_sound)
            d.message_queue.append(('Missed!',self.c.image,COL_LIGHT))

    def describe_resolution(self):
        return 'Attack a front row target of your choice. If you hit and penetrate its armor, and the target is on less than 30% max health and has less than 40 health, it dies immediately'

    def reflex_level(self):
        return 5

    def needed(self):
        for m in e.mobs:
            if m.alive and not m.about_to_die and m.front_row() and (m.health <= 0.3 * m.max_health) and (m.health <= 40):
                return True
        return False

    def ok(self):        
        return Character_action.ok(self) and self.needed()

    def reflex_target(self):
        for m in e.mobs:
            if m.alive and not m.about_to_die and m.front_row() and (m.health <= 0.3 * m.max_health) and (m.health <= 40):
                return m
        return None

    def describe_reflex_condition(self):
        return 'an enemy is near death'
    
class Swipe_CA(Character_action):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Swipe'

    def back_row_ok(self):
        return False

    def base_cost(self):
        return 10

    def cooldown(self):
        return 4.0

    def accuracy(self):
        return -3

    def delay(self):
        return 0.4

    def char_is_aggressive(self):
        for action in self.c.actions:
            if action.action_code == CA_AGGRESSIVE:
                return True
        return False

    def ok(self):
        if e.n_surviving_front_mobs()==0:
            return False
        return Character_action.ok(self)

    def make_stuff_happen(self,target):
        hits = 0
        self.c.accuracy = self.c.accuracy + self.accuracy()
        for target in [e.get_monster_at_pos(POS_M_FRONT_1),e.get_monster_at_pos(POS_M_FRONT_2),e.get_monster_at_pos(POS_M_FRONT_3)]:
            if target is not None:
                if self.c.melee_hit_roll(target):
                    target.take_damage(max(0,self.c.melee_damage_roll(target)-target.current_armor()))
                    hits = hits + 1
        self.c.accuracy = self.c.accuracy - self.accuracy()
        if hits>0:
            d.sound_queue.append(S_SMITE)
            #d.message_queue.append(('SWOOSH!',self.c.image,COL_LIGHT))
        else:
            d.sound_queue.append(self.c.melee_miss_sound)
            d.message_queue.append(('Missed!',self.c.image,COL_LIGHT))

    def describe_resolution(self):
        return 'Attack all front row targets at once, at -3 accuracy'

    def needed(self):
        if self.char_is_aggressive():
            if self.c.energy < ((2 * self.c.max_energy) / 3):
                return False
            count = 0
            for m in e.mobs:
                if m.alive and (not m.about_to_die) and m.front_row():
                    count = count + 1
            if count==3:
                return True
        return False

    def describe_reflex_condition(self):
        if self.char_is_aggressive():
            return 'you have plenty of energy and there are three front-row opponents before you'
        else:
            return None
    
class Cleave_CA(Swipe_CA):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Cleave'

    def base_cost(self):
        return 15

    def accuracy(self):
        return 0

    def describe_resolution(self):
        return 'Attack all front row targets at once, with no loss of accuracy'

class Throw_CA(Character_action):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Hurled Axe'

    def delay(self):
        return 1.0

    def front_row_ok(self):
        return False

    def target_enemy(self):
        return True

    def enemy_target_back_row_ok(self):
        return False

    def base_cost(self):
        return 5

    def make_stuff_happen(self,target):
        needed = 2 * (5 + self.c.skill/2 - target.current_evasion())
        if randint(1,20)<=needed:
            #d.message_queue.append(('Whizz... THUNK!',self.c.image,COL_LIGHT))
            target.take_damage(max(0,randint(2,16)-target.current_armor()))
        else:
            d.log('Misses!')
            d.message_queue.append(('Missed!',self.c.image,COL_LIGHT))

    def describe_resolution(self):
        return 'A missile attack doing 2-16 damage to a front row target, if it hits'
        
class Deflect_CA(Character_action):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Deflect'

    def back_row_ok(self):
        return False

    def base_cost(self):
        return 3

    def make_stuff_happen(self,target):
        self.c.current_stance = ST_DEFLECT
        self.c.stance_wears_off = w.elapsed_since_beginning + 4

    def pause(self):
        return 0.5
    
    def needed(self):
        for m in e.mobs:
            if m.alive and not (m.about_to_die) and m.action_in_progress is not None and m.action_in_progress.deflectable():
                if m.action_fires <= w.elapsed_since_beginning + self.pause():
                    return True
        return False

    def reflex_level(self):
        return 3

    def describe_resolution(self):
        return 'Block a special missile attack'

    def describe_reflex_condition(self):
        return 'required'

class Invulnerability_CA(Character_action):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Forcefield'

    def disruptable(self):
        return True

    def delay(self):
        return 1.0

    def message(self):
        return '' # 'Shazam!'

    def base_cost(self):
        return 10

    def make_stuff_happen(self,target):
        self.c.current_stance = ST_INVULNERABLE
        self.c.stance_wears_off = w.elapsed_since_beginning + self.duration()

    def duration(self):
        return min(2.0,0.4*self.c.power) 

    def describe_resolution(self):
        return 'Makes the entire party immune from physical damage for ' + str(round_to_1_dp(self.duration()/TIME_SCALAR)) + ' seconds'
    
class DeflectMissile_CA(Deflect_CA):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Avert Missile'

    def pause(self):
        return 1

    def base_cost(self):
        return 5
    
    def back_row_ok(self):
        return True

    def front_row_ok(self):
        return False

    def reflex_level(self):
        return 5

class Block_CA(Character_action):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Block'

    def back_row_ok(self):
        return False

    def base_cost(self):
        return 3

    def make_stuff_happen(self,target):
        self.c.current_stance = ST_BLOCK
        self.c.stance_wears_off = w.elapsed_since_beginning + 4

    def needed(self):
        if self.c.current_stance==ST_EPIC_BLOCK:
            return False
        for m in e.mobs:
            if m.alive and not (m.about_to_die) and m.action_in_progress is not None and (m.action_in_progress.blockable() or m.action_in_progress.semi_blockable()):
                if m.action_fires <= w.elapsed_since_beginning + 0.5:
                    return True
        return False

    def describe_resolution(self):
        return 'Block a Smite attack'

    def describe_reflex_condition(self):
        return 'required'

class Epic_Block_CA(Block_CA):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Epic Block'

    def base_cost(self):
        return 8

    def make_stuff_happen(self,target):
        self.c.current_stance = ST_EPIC_BLOCK
        self.c.stance_wears_off = w.elapsed_since_beginning + self.duration()

    def duration(self):
        return 4.0
    
    def reflex_level(self):
        return 5
    
    def needed(self):
        for m in e.mobs:
            if m.alive and not (m.about_to_die) and m.action_in_progress is not None and m.action_in_progress.epic_blockable():
                if m.action_fires <= w.elapsed_since_beginning + 0.5:
                    return True
        return False

    def describe_resolution(self):
        return 'Lasts for ' + str(round_to_1_dp(self.duration()/TIME_SCALAR)) + ' seconds. During this time you block all Smite attacks. Super Smite is mostly blocked but will still cause 20% normal damage.'

class Leap_Aside_CA(Block_CA):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Leap Aside'
    
    def base_cost(self):
        return 6

    def make_stuff_happen(self,target):
        self.c.current_stance = ST_LEAP_ASIDE
        self.c.stance_wears_off = w.elapsed_since_beginning + 1.2

    def reflex_level(self):
        return 5
    
    def needed(self):
        for m in e.mobs:
            if m.alive and not (m.about_to_die) and m.action_in_progress is not None and m.action_in_progress.leap_awayable():
                if m.action_fires <= w.elapsed_since_beginning + 0.5:
                    return True
        return False

    def describe_resolution(self):
        return 'Evades a Smite, Super Smite or thrown boulder attack. However, you have to time it just right.'
    
class Riposte_CA(Character_action):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Riposte'

    def back_row_ok(self):
        return False

    def base_cost(self):
        return 4

    def make_stuff_happen(self,target):
        self.c.current_stance = ST_RIPOSTE
        self.c.stance_wears_off = w.elapsed_since_beginning + 4

    def needed(self):
        for m in e.mobs:
            if m.alive and not (m.about_to_die) and m.action_in_progress is not None and m.action_in_progress.blockable():
                if m.action_fires <= w.elapsed_since_beginning + 0.5:
                    return True
        return False

    def reflex_level(self):
        return 2
    
    def describe_resolution(self):
        return 'Block a Smite attack and counterattack immediately. Not always successful, especially against more powerful opponents, but more likely to work if your Daring is high'

    def describe_reflex_condition(self):
        return 'required'
    
class Retrench_CA(Character_action):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Retrench'

    def needed(self):
        if self.c.auto_retrench and self.c.energy < self.c.max_energy:
            if e.n_surviving_mobs()==0:
                return True
        return False

    def reflex_level(self):
        return 3

    def make_stuff_happen(self,target):
        self.c.current_stance = ST_RETRENCH
        self.c.stance_wears_off = w.elapsed_since_beginning + 10

    def describe_resolution(self):
        return 'Triples your bar regeneration rate, but prevents you from autofighting and wears off if you act'     

class Ruthless_CA(Character_action):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Ruthless'

    def callable(self):
        return False

    def describe_resolution(self):
        return 'Prefer to attack weakened enemies, hopefully finishing them off'

class Aggressive_CA(Character_action):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Aggressive'

    def callable(self):
        return False

    def describe_resolution(self):
        return 'Use Bash, Smite, Swipe and Cleave by reflex if you have them'

class FireResistance_CA(Character_action):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Resist Fire'

    def base_cost(self):
        return 5

    def make_stuff_happen(self,target):
        self.c.status_effects[EF_RESIST_FIRE] = Effect_parameters(60)

    def describe_resolution(self):
        return 'Protect yourself from fire, halving damage for ' + str(round_to_1_dp(60/TIME_SCALAR)) + ' seconds'

    def reflex_level(self):
        return 3

    def needed(self):
        if self.c.has_status_effect(EF_RESIST_FIRE):
            return False
        for m in e.mobs:
            if m.alive and not (m.about_to_die) and m.action_in_progress is not None and m.action_in_progress.fiery():
                if m.action_fires <= w.elapsed_since_beginning + 0.5:
                    return True
        return False

    def describe_reflex_condition(self):
        return 'a monster is about to use a powerful fire attack against you'

class PoisonResistance_CA(Character_action):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Resist Poison'

    def base_cost(self):
        return 5

    def make_stuff_happen(self,target):
        self.c.status_effects[EF_RESIST_POISON] = Effect_parameters(60)

    def describe_resolution(self):
        return 'Protect yourself from poisoning for ' + str(round_to_1_dp(60/TIME_SCALAR)) + ' seconds'

    def reflex_level(self):
        return 3

    def needed(self):
        if self.c.has_status_effect(EF_RESIST_POISON):
            return False
        for c in p.chars:
            if c.alive and not c.about_to_die and c.has_status_effect(EF_POISON):
                return True
        return False

    def describe_reflex_condition(self):
        return 'any character is poisoned'
    
class Leader_CA(Character_action):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Leader'

    def callable(self):
        return False

    def describe_resolution(self):
        return 'Gain +1 speed if you are positioned in the center of the front row'

class Armor_CA(Character_action):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Faith Armored'

    def callable(self):
        return False

    def describe_resolution(self):
        return 'Gain +1 armor at all times'

class Concentration_CA(Character_action):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Concentration'

    def callable(self):
        return False

    def describe_resolution(self):
        return 'You can take a small amount of damage without disrupting your spell-in-progress'
    
class Penetration_CA(Character_action):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Crack Shot'

    def callable(self):
        return False

    def describe_resolution(self):
        return 'Gain +10% base damage with missile weapons'

class Inconspicuous_CA(Character_action):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Inconspicuous'

    def callable(self):
        return False

    def describe_resolution(self):
        return 'Gain +1 evasion and -1 aggro at all times'

class Healer_CA(Character_action):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Healer'

    def callable(self):
        return False

    def describe_resolution(self):
        return 'This priest can regain bar by casting healing spells, instead of by killing evil enemies. (It especially helps to have this ability if you have more than one priest in the party.)'

class Assassin_CA(Character_action):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Assassin'

    def callable(self):
        return False

    def describe_resolution(self):
        return 'This rogue can regain bar by hitting monsters with his basic melee attack, rather than by getting gold. (It especially helps to have this ability if you have more than one rogue in the party.)'

class Archer_CA(Character_action):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Archer'

    def callable(self):
        return False

    def describe_resolution(self):
        return 'This rogue can regain bar by shooting monsters with his basic missile attack, rather than by getting gold. (It especially helps to have this ability if you have more than one rogue in the party.)'
    
class Righteousness_CA(Character_action):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Righteous'

    def base_cost(self):
        return 3    

    def make_stuff_happen(self,target):
        self.c.status_effects[EF_RIGHTEOUSNESS] = Effect_parameters(60)

    def describe_resolution(self):
        return 'Increase Evasion and Aggro by 1 for ' + str(round_to_1_dp(60/TIME_SCALAR)) + ' seconds'

class InsultYourMother_CA(Character_action):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Insult Your Mother'

    def base_cost(self):
        return 3    

    def make_stuff_happen(self,target):
        self.c.status_effects[EF_INSULT] = Effect_parameters(20)

    def describe_resolution(self):
        return 'Increase Aggro by 2 for ' + str(round_to_1_dp(20/TIME_SCALAR)) + ' seconds'

class FindWeakness_CA(Character_action):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Find Weakness'

    def base_cost(self):
        return 10    

    def make_stuff_happen(self,target):
        self.c.status_effects[EF_FIND_WEAKNESS] = Effect_parameters(15)

    def describe_resolution(self):
        return 'Your melee attacks ignore 5 points of the victim\'s armor for ' + str(round_to_1_dp(15/TIME_SCALAR)) + ' seconds'
    
class Aggro_CA(Character_action):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Jehovah\'s Witness'

    def callable(self):
        return False

    def describe_resolution(self):
        return 'Gain +1 aggro at all times'

class Rosary_CA(Character_action):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Rosary'

    def callable(self):
        return False

    def describe_resolution(self):
        return 'This priest can regain bar over time, instead of by killing evil enemies. (It especially helps to have this ability if you have more than one priest in the party.)'

class Taunt_CA(Character_action):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Taunt'

    def base_cost(self):
        return 3    

    def make_stuff_happen(self,target):
        self.c.status_effects[EF_TAUNT] = Effect_parameters(30)

    def describe_resolution(self):
        return 'Increase Aggro by 1 for ' + str(round_to_1_dp(30/TIME_SCALAR)) + ' seconds'
    
class DragonSlayer_CA(Character_action):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Dragon Slayer'

    def callable(self):
        return False

    def describe_resolution(self):
        return 'Player gets +100% base melee damage against dragons'

class Snipe_CA(Character_action):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Snipe'

    def target_enemy(self):
        return True

    def front_row_ok(self):
        return False

    def delay(self):
        return 1.0

    def cooldown(self):
        return 0.5

    def base_cost(self):
        return 4 

    def make_stuff_happen(self,target):
        if self.c.missile_hit_roll(target):
            d.sound_queue.append(self.c.missile_hit_sound)
            #d.message_queue.append(('Twang!',self.c.image,COL_LIGHT))
            target.take_damage(max(0,self.c.missile_damage_roll(target)-target.current_armor()))
            target.has_been_shot = True
        else:
            d.log('Misses!')
            d.sound_queue.append(self.c.missile_miss_sound)
            d.message_queue.append(('Missed!',self.c.image,COL_LIGHT))

    def needed(self):
        for m in e.mobs:
            if m.alive and (not m.about_to_die) and m.action_in_progress is not None and m.action_in_progress.disruptable():
                return True
        return False

    def reflex_target(self):
        for m in e.mobs:
            if m.alive and (not m.about_to_die) and m.action_in_progress is not None and m.action_in_progress.disruptable():
                return m
        return None       

    def describe_reflex_condition(self):
        return 'a monster is trying to cast a spell that can be disrupted'

    def describe_resolution(self):
        return 'Shoot a target of your choice (with normal accuracy and damage)'

class LuckyShot_CA(Snipe_CA):
    def __init__(self):
        Character_action.__init__(self)

    def name(self):
        return 'Armor Piercing Shot'
    
    def cooldown(self):
        return 6.0

    def base_cost(self):
        return 7

    def make_stuff_happen(self,target):
        if self.c.missile_hit_roll(target):
            d.sound_queue.append(self.c.missile_hit_sound)
            #d.message_queue.append(('Twang!',self.c.image,COL_LIGHT))
            target.take_damage(self.c.missile_damage_roll(target))
            if not target.front_row():
                target.has_been_shot = True
        else:
            d.log('Misses!')
            d.sound_queue.append(self.c.missile_miss_sound)
            d.message_queue.append(('Missed!',self.c.image,COL_LIGHT))

    def describe_resolution(self):
        return 'Shoot a target of your choice, ignoring armor'

    def needed(self):
        return False

class AccurateShot_CA(Snipe_CA):
    def __init__(self):
        Character_action.__init__(self)

    def name(self):
        return 'Accurate Shot'

    def base_cost(self):
        return 7

    def cooldown(self):
        return 3.0

    def make_stuff_happen(self,target):
        self.c.accuracy = self.c.accuracy + 6
        if self.c.missile_hit_roll(target):
            d.sound_queue.append(self.c.missile_hit_sound)
            #d.message_queue.append(('Twang!',self.c.image,COL_LIGHT))
            target.take_damage(max(0,self.c.missile_damage_roll(target)-target.current_armor()))
            if not target.front_row():
                target.has_been_shot = True
        else:
            d.log('Misses!')
            d.sound_queue.append(self.c.missile_miss_sound)
            d.message_queue.append(('Missed!',self.c.image,COL_LIGHT))
        self.c.accuracy = self.c.accuracy - 6

    def describe_resolution(self):
        return 'Shoot a target of your choice, with +6 accuracy'

    def needed(self):
        return False
    
class LuckyEscape_CA(Character_action):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Lucky Escape'

    def callable(self):
        return False

    def success_chance(self):
        return success_chance(self.c.cunning + 3)

    def describe_resolution(self):
        return 'Automatically avoid any attack doing 10-20 damage (costs 12 bar, ' + str(self.success_chance()) + '% likely to work)'

class SteelyGaze_CA(Character_action):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Steely Gaze'

    def callable(self):
        return False

    def describe_resolution(self):
        return 'Makes this character immune to all gaze attacks'

class FreeAction_CA(Character_action):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Free Action'

    def callable(self):
        return False

    def describe_resolution(self):
        return 'Makes this character immune to all paralysis attacks'

class ResistFear_CA(Character_action):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Resist Fear'

    def callable(self):
        return False

    def describe_resolution(self):
        return 'Makes this character immune to all fear attacks'

class UndeadSlayer_CA(Character_action):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Undead Slayer'

    def callable(self):
        return False

    def describe_resolution(self):
        return 'Doubles your base damage against undead'

class Magebane_CA(Character_action):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Mage Bane'

    def callable(self):
        return False

    def describe_resolution(self):
        return 'Makes you much more likely to hit when you shoot a spellcaster who has used Deflect Missiles'

class Veteran_CA(Character_action):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Veteran'

    def callable(self):
        return False

    def describe_resolution(self):
        return 'This warrior will Retrench to regain bar, without being told to, if there are no enemies around. Works much better if he has high Reflexes'

class Rage_CA(Character_action):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Rage'

    def callable(self):
        return False

    def describe_resolution(self):
        return 'This warrior gains bar whenever he takes damage. Works well with Berserk'

class WandLore_CA(Character_action):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Wand Lore'

    def callable(self):
        return False

    def describe_resolution(self):
        return 'Gives you +1 Aim and +1 Rapidity with wand attacks'

class WandLore2_CA(Character_action):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Advanced Wand Lore'

    def callable(self):
        return False

    def describe_resolution(self):
        return 'Gives you +2 Aim and +2 Rapidity with wand attacks'
    
class Dodge_CA(Character_action):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Dodge'

    def base_cost(self):
        return 5    

    def make_stuff_happen(self,target):
        self.c.status_effects[EF_DODGE] = Effect_parameters(20)

    def describe_resolution(self):
        return 'Increase Evasion by 2 for ' + str(round_to_1_dp(20/TIME_SCALAR)) + ' seconds'

class Meditation_CA(Character_action):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Meditation'

    def base_cost(self):
        return 4    

    def make_stuff_happen(self,target):
        self.c.status_effects[EF_MEDITATION] = Effect_parameters(20)

    def describe_resolution(self):
        return 'Protect this character (only) from sleep, paralysis, stunning and fear for ' + str(round_to_1_dp(20/TIME_SCALAR)) + ' seconds'

class SlowTime_CA(Spell_CA):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Slow Time'

    def base_cost(self):
        return 3

    def make_stuff_happen(self,target):
        w.slow_time_until = w.elapsed_since_beginning + 7.0
        
    def describe_resolution(self):
        return 'Slows the pace of time for a few subjective seconds, allowing you to plan your next moves'

class BuffEV_CA(Spell_CA):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Warding'

    def target_friend(self):
        return True

    def base_cost(self):
        return 6    

    def amount(self):
        return (self.c.subtlety-1)/3+1

    def make_stuff_happen(self,target):
        effect = Effect_parameters(20)
        effect.level = self.amount()
        target.status_effects[EF_EV] = effect

    def describe_resolution(self):
        return 'Increase the target\'s Evasion by ' + str(self.amount()) + ', for ' + str(round_to_1_dp(20/TIME_SCALAR)) + ' seconds'

class ForceBrand_CA(Spell_CA):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Force Brand'

    def base_cost(self):
        return 4    

    def amount(self):
        return (self.c.power-1)/3+1

    def make_stuff_happen(self,target):
        effect = Effect_parameters(30)
        effect.level = self.amount()
        self.c.status_effects[EF_FORCEBRAND] = effect

    def describe_resolution(self):
        return 'Increase your melee damage by ' + str(self.amount()*10) + '% of your base damage, for ' + str(round_to_1_dp(30/TIME_SCALAR)) + ' seconds'

class Shield_CA(Spell_CA):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Shield'

    def base_cost(self):
        return 6   

    def amount(self):
        return (self.c.power-1)/3+1

    def make_stuff_happen(self,target):
        effect = Effect_parameters(30)
        effect.level = self.amount()
        self.c.status_effects[EF_SHIELD] = effect

    def describe_resolution(self):
        return 'Increase your armor rating by ' + str(self.amount()) + ', for ' + str(round_to_1_dp(30/TIME_SCALAR)) + ' seconds'
        
class Missile_CA(Snipe_CA):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Magic Missile'

    def delay(self):
        return 2.0

    def cooldown(self):
        return 5.0

    def base_cost(self):
        return 5 

    def hit_chance(self):
        return success_chance(self.c.subtlety + 3)

    def min_damage(self):
        return 8

    def max_damage(self):
        return 16

    def make_stuff_happen(self,target):
        if randint(1,100) <= self.hit_chance():
            d.sound_queue.append(self.c.missile_hit_sound)
            #d.message_queue.append(('Zap!',self.c.image,COL_LIGHT))
            target.take_damage(max(0,randint(self.min_damage(),self.max_damage())-target.current_armor()))
            target.flash_image = I_MISSILE
        else:
            d.log('Misses!')
            d.sound_queue.append(self.c.missile_miss_sound)
            d.message_queue.append(('Missed!',self.c.image,COL_LIGHT))

    def needed(self):
        return False
    
    def describe_resolution(self):
        return 'Zap a target of your choice, with a ' + str(int(round(self.hit_chance()))) + '% chance of hitting for ' + str(self.min_damage()) + '-' + str(self.max_damage()) + ' damage (before armor)'
    
class TakeCover_CA(Character_action):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Take Cover!'

    def base_cost(self):
        return 15

    def success_chance(self):
        return success_chance(self.c.cunning + 5)

    def make_stuff_happen(self,target):
        for c in p.chars:
            if c.alive and not c.about_to_die:
                c.current_stance = ST_TAKE_COVER
                c.stance_wears_off = w.elapsed_since_beginning + 5
        p.take_cover_success_chance = self.success_chance() # kludge!

    def needed(self):
        for m in e.mobs:
            if m.alive and not (m.about_to_die) and m.action_in_progress is not None and m.action_in_progress.take_coverable():
                if m.action_fires <= w.elapsed_since_beginning + 0.5:
                    return True
        return False

    def reflex_level(self):
        return 5

    def describe_resolution(self):
        return 'Protect party from breath weapons, lightning bolts and thrown boulders for ' + str(round_to_1_dp(5/TIME_SCALAR)) + ' seconds. '+str(self.success_chance())+'% likely to work'

    def describe_reflex_condition(self):
        return 'required'

class FindTraps_CA(Character_action):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Find Traps'

    def base_cost(self):
        return 3    

    def make_stuff_happen(self,target):
        self.c.status_effects[EF_REVEAL_DANGER] = Effect_parameters(10)

    def describe_resolution(self):
        return 'Protect from traps for ' + str(round_to_1_dp(10/TIME_SCALAR)) + ' seconds'

    def needed(self):
        return w.traps_here and not self.c.has_status_effect(EF_REVEAL_DANGER)

    def reflex_level(self):
        return 3

    def describe_reflex_condition(self):
        return 'a trap is nearby'

class MoreGold_CA(Character_action):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Looter'

    def callable(self):
        return False

    def increase(self):
        return (20 + self.c.cunning) / 20.0

    def describe_resolution(self):
        return 'Find ' + str(int(round(100*(self.increase()-1)))) + '% extra gold'

class MoreGems_CA(Character_action):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Lapidary'

    def callable(self):
        return False

    def increase(self):
        return (10 + self.c.cunning) / 10.0

    def describe_resolution(self):
        return 'Find ' + str(int(round(100*(self.increase()-1)))) + '% extra gems'

class PickLocks_CA(Character_action):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Pick Locks'

    def callable(self):
        return False

    def chance(self):
        return success_chance(self.c.cunning + 4)

    def describe_resolution(self):
        return str(self.chance()) + ' chance of picking locks'
        
class ResistFire_CA(Spell_CA):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Protect from Fire'

    def base_cost(self):
        return 6

    def make_stuff_happen(self,target):
        for c in p.chars:
            if c.alive and not c.about_to_die:
                c.status_effects[EF_RESIST_FIRE] = Effect_parameters(30)

    def describe_resolution(self):
        return 'Protect party from fire, halving damage for ' + str(round_to_1_dp(30/TIME_SCALAR)) + ' seconds'

class Sacrifice_CA(Character_action):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Sacrifice'

    def delay(self):
        return 1.0

    def make_stuff_happen(self,target):
        energy_gain = 0
        if self.c.energy_for_evil_kill:
            for time in w.evil_kills:
                if time >= w.elapsed_since_beginning-5:
                    energy_gain = energy_gain + 3
        w.evil_kills = []
        if energy_gain>0:
            self.c.energy = min(self.c.max_energy,self.c.energy+energy_gain);
            d.sound_queue.append(S_GOOD_SPELL)
            d.message_queue.append(('The Gods are pleased!',self.c.image,COL_LIGHT))
        else:
            d.sound_queue.append(S_CANCEL)
            d.message_queue.append(('The Gods are unimpressed!',self.c.image,COL_LIGHT))
        d.log('Regains ' + str(energy_gain))
            
    def needed(self):
        if self.c.energy_for_evil_kill and self.c.energy<(self.c.max_energy-3) and len(w.evil_kills)>0 and max(w.evil_kills) < w.elapsed_since_beginning-3.5 and max(w.evil_kills) >= w.elapsed_since_beginning-4:
            return True
        return False

    def reflex_level(self):
        return 3

    def describe_resolution(self):
        return 'Gain 3 bar for each evil enemy killed in the last ' + str(round_to_1_dp(5/TIME_SCALAR)) + ' seconds'

    def describe_reflex_condition(self):
        return 'an evil enemy has died recently'

class Revitalise_CA(Spell_CA):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Revitalise'

    def target_friend(self):
        return True

    def base_cost(self):
        return 2   

    def make_stuff_happen(self,target):
        d.sound_queue.append(S_GOOD_SPELL)
        transferred = min(10,self.c.energy)
        transferred = min(transferred,target.max_energy-target.energy)
        target.energy = target.energy + transferred
        self.c.energy = self.c.energy - transferred
        d.log('Transfers ' + str(transferred) + ' bar')

    def describe_resolution(self):
        return 'Transfer up to 10 points from your energy bar to another character\'s energy bar'

class Healing_CA(Spell_CA):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Cure Wounds'

    def target_friend(self):
        return True

    def base_cost(self):
        return 12   

    def recover(self):
        return 4

    def min_heal(self):
        return ((self.c.wisdom+2) * 7) / 6

    def max_heal(self):
        return ((self.c.wisdom+2) * 15) / 6

    def make_stuff_happen(self,target):
        d.sound_queue.append(S_HEALING)
        old_health = target.health
        target.health = min(target.health+randint(self.min_heal(),self.max_heal()),target.max_health)
        if target.health > old_health:
            target.flash_number = target.health - old_health
            target.number_starts = w.elapsed_since_beginning
            target.number_color = [0, 255, 0]
            d.log('Regains ' + str(target.flash_number) + ' health')
        if self.c.energy_for_healing:
            self.c.energy = min(self.c.max_energy,self.c.energy + self.recover())

    def needed(self):
        for c in p.chars:
            if c<>self.c and c.alive and (not c.about_to_die) and c.health < (self.criterion()*c.max_health):
                return True
        return False

    def criterion(self):
        return 0.4

    def reflex_target(self):
        for c in p.chars:
            if c<>self.c and c.alive and (not c.about_to_die) and c.health < (self.criterion()*c.max_health):
                return c
        return None       

    def describe_resolution(self):
        explanation = 'Heal another character for '+str(self.min_heal())+'-'+str(self.max_heal())+' health'
        if self.c.energy_for_healing:
            explanation = explanation + ' and regain ' + str(self.recover()) + ' bar'
        return explanation

    def describe_reflex_condition(self):
        return 'another character\'s health drops below ' + str(int(round(100*self.criterion()))) + '%'

class KnitFlesh_CA(Healing_CA):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Knit Flesh'

    def criterion(self):
        return 0.2

    def min_heal(self):
        return ((self.c.subtlety+2) * 7) / 8

    def max_heal(self):
        return ((self.c.subtlety+2) * 15) / 8

class SelfHeal_CA(Healing_CA):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Heal Self'

    def target_friend(self):
        return False
    
    def needed(self):
        if self.c.health < (self.criterion()*self.c.max_health):
            return True
        return False

    def recover(self):
        return 0

    def make_stuff_happen(self,target):
        Healing_CA.make_stuff_happen(self,self.c)

    def criterion(self):
        return 0.4

    def reflex_target(self):
        return None       

    def describe_resolution(self):
        return 'Heal yourself for '+str(self.min_heal())+'-'+str(self.max_heal())+' health'

    def describe_reflex_condition(self):
        return 'your health drops below ' + str(100*self.criterion()) + '%'

class SmallHeal_CA(Healing_CA):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Cure Light Wounds'

    def delay(self):
        return 1.5
    
    def base_cost(self):
        return 5   

    def recover(self):
        return 2

    def min_heal(self):
        return ((self.c.wisdom+2) * 3) / 6

    def max_heal(self):
        return ((self.c.wisdom+2) * 7) / 6

    def criterion(self):
        return 0.7

class BigHeal_CA(Healing_CA):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Cure Serious Wounds'

    def delay(self):
        return 3.0

    def base_cost(self):
        return 25   

    def recover(self):
        return 7

    def min_heal(self):
        return ((self.c.wisdom+2) * 13) / 6

    def max_heal(self):
        return ((self.c.wisdom+2) * 27) / 6

class MassHeal_CA(Healing_CA):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Mass Healing'

    def delay(self):
        return 3.0
    
    def target_friend(self):
        return False

    def base_cost(self):
        return 25   

    def recover(self):
        return 7

    def min_heal(self):
        return ((self.c.wisdom+2) * 5) / 6

    def max_heal(self):
        return ((self.c.wisdom+2) * 9) / 6

    def make_stuff_happen(self,target):
        d.sound_queue.append(S_HEALING)
        for target in p.chars:
            old_health = target.health
            target.health = min(target.health+randint(self.min_heal(),self.max_heal()),target.max_health)
            if target.health > old_health:
                target.flash_number = target.health - old_health
                target.number_starts = w.elapsed_since_beginning
                target.number_color = [0, 255, 0]
                d.log('Regains ' + str(target.flash_number) + ' health')
        if self.c.energy_for_healing:
            self.c.energy = min(self.c.max_energy,self.c.energy + self.recover())
            
    def needed(self):
        actual = 0
        max = 0
        for c in p.chars:
            if c.alive and not c.about_to_die:
                actual = actual + c.health
                max = max + c.max_health
        ratio = float(actual)/max
        if ratio < 0.5:
            return True
        return False

    def reflex_target(self):
        return None       

    def describe_resolution(self):
        explanation = 'Heal the entire party for '+str(self.min_heal())+'-'+str(self.max_heal())+' health'
        if self.c.energy_for_healing:
            explanation = explanation + ' and regain ' + str(self.recover()) + ' bar'
        return explanation
    
    def describe_reflex_condition(self):
        return 'the party drops below half its total health'   

class CureMortalWounds_CA(Spell_CA):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Cure Mortal Wounds'

    def target_friend(self):
        return True

    def friend_target_ok(self,c):
        if c==self.c:
            return False
        if not c.alive:
            return False
        if c.about_to_die:
            return True
        return False
    
    def base_cost(self):
        return 15   

    def delay(self):
        return 0.1

    def make_stuff_happen(self,target):
        if target.alive and target.about_to_die:
            d.sound_queue.append(S_HEALING)
            old_health = 0
            target.health = target.max_health / 5
            target.about_to_die = False
            target.flash_number = target.health - old_health
            target.number_starts = w.elapsed_since_beginning
            target.number_color = [0, 255, 0]
            d.log('Revived to ' + str(target.flash_number) + ' health')
            if self.c.energy_for_healing:
                self.c.energy = min(self.c.max_energy,self.c.energy + self.recover())

    def recover(self):
        return 3

    def ok(self):
        return Character_action.ok(self) and self.needed()
    
    def needed(self):
        if self.reflex_target() is not None:
            return True
        return False

    def reflex_level(self):
        return 5
    
    def reflex_target(self):
        for c in p.chars:
            if c<>self.c and c.alive and c.about_to_die:
                return c
        return None       

    def describe_resolution(self):
        explanation = 'Save another character from death'
        if self.c.energy_for_healing:
            explanation = explanation + ' and regain ' + str(self.recover()) + ' bar'
        explanation = explanation + '. Use this spell quickly when the skull and crossbones appear...'
        return explanation

    def describe_reflex_condition(self):
        return 'another character\'s health drops below 0'
    
class CurePoison_CA(Spell_CA):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Cure Poison'

    def base_cost(self):
        return 3

    def target_friend(self):
        return True

    def ok(self):
        return Character_action.ok(self) and self.needed()      

    def make_stuff_happen(self,target):
        d.sound_queue.append(S_GOOD_SPELL)
        if target.has_status_effect(EF_POISON):
            target.status_effects[EF_POISON].level = 0
            target.status_effects[EF_POISON].lasts_until = 0

    def needed(self):
        if self.reflex_target() is not None:
            return True
        return False

    def reflex_target(self):
        if w.poison_cloud_until is not None:
            if w.poison_cloud_until > w.elapsed_since_beginning:
                return None
        for c in p.chars:
            if c.alive and not c.about_to_die and c.has_status_effect(EF_POISON) and c.status_effects[EF_POISON].level>0:
                return c
        return None
    
    def reflex_level(self):
        return 4

    def describe_resolution(self):
        return 'Cure the target of all poisoning'

    def describe_reflex_condition(self):
        return 'a character is poisoned'

class CureFear_CA(CurePoison_CA):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Cure Fear'

    def base_cost(self):
        return 3

    def make_stuff_happen(self,target):
        d.sound_queue.append(S_GOOD_SPELL)
        if target.has_status_effect(EF_FEAR):
            target.status_effects[EF_FEAR].lasts_until = 0

    def reflex_target(self):
        for c in p.chars:
            if c.alive and not c.about_to_die and c.has_status_effect(EF_FEAR):
                return c
        return None
    
    def describe_resolution(self):
        return 'Cure the target of magical fear'

    def describe_reflex_condition(self):
        return 'a character is afraid'
    
class FrontRowBuff_CA(Character_action):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Exhort'

    def base_cost(self):
        return 12    

    def make_stuff_happen(self,target):
        for c in p.chars:
            if c.alive and not c.about_to_die and c.front_row():
                c.status_effects[EF_FRONTROWBUFF] = Effect_parameters(30)

    def describe_resolution(self):
        return 'All front row characters gain +30% base damage for ' + str(round_to_1_dp(30/TIME_SCALAR)) + ' seconds'

class RevealDanger_CA(Character_action):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Reveal Danger'

    def base_cost(self):
        return 3    

    def make_stuff_happen(self,target):
        self.c.status_effects[EF_REVEAL_DANGER] = Effect_parameters(10)

    def describe_resolution(self):
        return 'Protect from traps for ' + str(round_to_1_dp(10/TIME_SCALAR)) + ' seconds'

class AllOutDefense_CA(Character_action):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'All-Out Defense'

    def base_cost(self):
        return 7    

    def make_stuff_happen(self,target):
        self.c.current_stance = ST_ALL_OUT_DEFENSE
        self.c.stance_wears_off = w.elapsed_since_beginning + 10

    def describe_resolution(self):
        return 'Increase Evasion and Armor by 3 for ' + str(round_to_1_dp(10/TIME_SCALAR)) + ' seconds. You do not attack as normal, and if you use any ability, you lose the defensive bonuses'

class TurnUndead_CA(Spell_CA):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Turn Undead'

    def cooldown(self):
        return 5.0
    
    def message(self):
        return 'Begone, foul creatures of darkness!'

    def base_cost(self):
        return 6

    def ok(self):
        if Character_action.ok(self):
            for m in e.mobs:
                if m.alive and not m.about_to_die and m.undead and m.level<=6:
                    return True
        return False

    def make_stuff_happen(self,target):
        d.sound_queue.append(S_GOOD_SPELL)
        for m in e.mobs:
            if m.alive and not m.about_to_die and m.undead:
                if m.level<=6 and randint(1,100)<=success_chance(6 + self.c.wisdom/2 + self.c.level - 2*m.level):
                    m.flee()
                else:
                    d.message_queue.append(('Defies you',m.image,COL_LIGHT))

    def describe_resolution(self):
        return 'Banish one or more undead monsters. They are not slain and do not yield experience or treasure, but they will trouble you no more. More effective as your level and Wisdom increase, less effective on higher level monsters, and does not work at all against the most powerful undead'

class Dispell_CA(Spell_CA):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Banish'

    def cooldown(self):
        return 5.0
    
    def message(self):
        return 'Return to whence you came!'

    def base_cost(self):
        return 12

    def ok(self):
        if Character_action.ok(self):
            for m in e.mobs:
                if m.alive and not m.about_to_die and not m.construct and (m.summoner is not None or m.from_summoning_trap is True):
                    return True
        return False

    def make_stuff_happen(self,target):
        d.sound_queue.append(S_GOOD_SPELL)
        for m in e.mobs:
            if m.alive and not m.about_to_die and not m.construct and (m.summoner is not None or m.from_summoning_trap is True):
                if randint(1,100)<=success_chance(9 + self.c.level - 2*m.level):
                    m.flee()
                else:
                    d.message_queue.append(('Defies you',m.image,COL_LIGHT))

    def describe_resolution(self):
        return 'Banish one or more summoned monsters. They are not slain and do not yield experience or treasure, but they will trouble you no more. More effective as your level increases, and less effective on higher level monsters. Golems are not affected'
    
class PoisonousCloud_CA(Spell_CA):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Poison Cloud'

    def message(self):
        return '' # 'Psssssssch...'

    def cooldown(self):
        return 3

    def base_cost(self):
        return 6

    def min_damage(self):
        return ((self.c.power+2) * 8) / 7

    def max_damage(self):
        return ((self.c.power+2) * 16) / 7

    def make_stuff_happen(self,target):
        d.sound_queue.append(S_GAS)
        for m in e.mobs:
            if m.alive and not m.about_to_die and m.front_row() and not m.poison_resistant:
                m.take_damage(randint(self.min_damage(),self.max_damage()))
                m.flash_image = I_POISON_CLOUD

    def describe_resolution(self):
        return 'Do '+str(self.min_damage())+'-'+str(self.max_damage())+' damage to all front row enemies. Armor is no protection but poison resistant enemies are immune'

class Shockwave_CA(Spell_CA):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Shockwave'

    def message(self):
        return '' # 'KABOOM'

    def back_row_ok(self):
        return False
    
    def base_cost(self):
        return 7

    def min_damage(self):
        return ((self.c.power+2) * 8) / 9

    def max_damage(self):
        return ((self.c.power+2) * 16) / 9

    def make_stuff_happen(self,target):
        d.sound_queue.append(S_GOOD_SPELL)
        for m in e.mobs:
            if m.alive and not m.about_to_die and m.front_row():
                m.take_damage(randint(self.min_damage(),self.max_damage()))
                m.flash_image = I_LIGHTNING
                d.sound_queue.append(S_LIGHTNING_BOLT)
                if m.alive and not m.about_to_die and not m.stun_resistant:
                    m.stunned = True
                    d.message_queue.append(('Stunned',m.image,COL_LIGHT))
                    duration = randint(2,8) * (2.0 / (2 + (m.max_health / 50.0)))
                    if m.dupe_until >= w.elapsed_since_beginning:
                        duration = duration * 2
                    m.stunned_until = w.elapsed_since_beginning + duration                    
                    m.action_in_progress = None

    def describe_resolution(self):
        return 'Does '+str(self.min_damage())+'-'+str(self.max_damage())+' damage to all front row enemies, and may also stun them if they are not stun resistant. Armor is no protection. Can only be used from the front row'

class Curse_CA(Spell_CA):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Curse'

    def message(self):
        return choice(['May you be eternally cast out!','The fiend take you!','A plague on you!','Foul creature of darkness!','Be damned both coming and going!'])
   
    def target_enemy(self):
        return True

    def enemy_target_back_row_ok(self):
        return False

    def base_cost(self):
        return 5

    def min_damage(self):
        return 5

    def max_damage(self):
        return 10

    def make_stuff_happen(self,target):
        d.sound_queue.append(S_GOOD_SPELL)
        if target.alive and not target.about_to_die:
            target.take_damage(randint(self.min_damage(),self.max_damage()))
            if target.alive and not target.about_to_die and not target.stun_resistant:
                target.stunned = True
                d.message_queue.append(('Stunned',target.image,COL_LIGHT))
                duration = randint(2,8) * (2.0 / (2 + (target.max_health / 50.0)))
                if target.dupe_until >= w.elapsed_since_beginning:
                    duration = duration * 2
                target.stunned_until = w.elapsed_since_beginning + duration                    
                target.action_in_progress = None

    def describe_resolution(self):
        return 'Does '+str(self.min_damage())+'-'+str(self.max_damage())+' damage to a single front-row enemy, and may also stun them if they are not stun resistant. Armor is no protection'

class Confusion_CA(Spell_CA):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Confusion'

    def target_enemy(self):
        return True

    def enemy_target_back_row_ok(self):
        return True # False

    def base_cost(self):
        return 6

    def make_stuff_happen(self,target):
        d.sound_queue.append(S_GOOD_SPELL)
        if target.alive and not target.about_to_die:
            resistance = target.level
            if target.confusion_resistant:
                resistance = resistance + 99
            for a in target.actions:
                if a.action_code==MA_GET_CONFUSED:
                    resistance = resistance - 2
            if target.dupe_until >= w.elapsed_since_beginning:
                resistance = resistance - 3
            diff = success_chance(6 + self.c.subtlety - resistance) - randint(1,100)
            if diff >= 0:
                target.confused = True
                d.message_queue.append(('Confused',target.image,COL_LIGHT))
                duration = randint(5,10)
                if diff >= 60:
                    duration = duration * 2.0
                elif diff >= 30:
                    duration = duration * 1.5
                target.confused_until = w.elapsed_since_beginning + duration                    
                target.action_in_progress = None
            else:
                d.message_queue.append(('Resists!',target.image,COL_LIGHT))

    def describe_resolution(self):
        return 'Confuses a single enemy. More likely to work if your Subtlety is high and the monster is not very high-level. Some monsters are immune; others (such as the stupider giants and ogres) may be particularly vulnerable'

class ConfusingSpiel_CA(Spell_CA):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Confusing Spiel'

    def message(self):
        return choice(('I once travelled to far Almany...','Three men walked into a tavern...','Now watch my hands carefully...','Let me spin you a tale...','You\'ll not top this, but...'))

    def target_enemy(self):
        return True

    def base_cost(self):
        return 3

    def make_stuff_happen(self,target):
        d.sound_queue.append(S_GOOD_SPELL)
        if target.alive and not target.about_to_die:
            if not target.confusion_resistant:
                target.dupe_until = w.elapsed_since_beginning + 30
            else:
                d.message_queue.append(('It doesn\'t believe your tall tale!',target.image,COL_LIGHT))

    def describe_resolution(self):
        return 'Your Rogue tells an improbable story to a monster.  The poor dupe becomes more vulnerable to your other characters\' stun and confusion attacks for the next few seconds'

class PoisonCloud2_CA(PoisonousCloud_CA):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Sparkling Cloud'

    def base_cost(self):
        return 12

    def min_damage(self):
        return ((self.c.power+2) * 12) / 7

    def max_damage(self):
        return ((self.c.power+2) * 24) / 7

    def make_stuff_happen(self,target):
        d.sound_queue.append(S_GAS)
        for m in e.mobs:
            if m.alive and not m.about_to_die and m.front_row():
                if not m.lightning_resistant:
                    m.flash_image = I_SPARKLING_CLOUD
                    m.take_damage(randint(self.min_damage(),self.max_damage()))
                else:
                    d.message_queue.append(('Immune to lightning!',m.image,COL_LIGHT))

    def describe_resolution(self):
        return 'Do '+str(self.min_damage())+'-'+str(self.max_damage())+' damage to all front row enemies. Neither armor nor poison resistance protects, but lightning resistance does'
 
class CounterspellParalysis_CA(Character_action):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Counter: Paralysis'

    def base_cost(self):
        return 2

    def disruptable(self):
        return True

    def make_stuff_happen(self,target):
        self.c.current_stance = ST_COUNTER_PARALYSIS
        self.c.stance_wears_off = w.elapsed_since_beginning + 4
        
    def needed(self):
        for m in e.mobs:
            if m.alive and not m.about_to_die and m.action_in_progress is not None and m.action_in_progress.paralysing():
                if m.action_fires <= w.elapsed_since_beginning + 0.5:
                    return True
        return False

    def reflex_level(self):
        return 5

    def describe_resolution(self):
        return 'Protects from paralysing spells and gazes for ' + str(round_to_1_dp(4/TIME_SCALAR)) + ' seconds'

    def describe_reflex_condition(self):
        return 'required'

class CounterFear_CA(CounterspellParalysis_CA):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Counter: Fear'

    def make_stuff_happen(self,target):
        self.c.current_stance = ST_COUNTER_FEAR
        self.c.stance_wears_off = w.elapsed_since_beginning + 4
        
    def needed(self):
        for m in e.mobs:
            if m.alive and not m.about_to_die and m.action_in_progress is not None and m.action_in_progress.scary():
                if m.action_fires <= w.elapsed_since_beginning + 0.5:
                    return True
        return False

    def describe_resolution(self):
        return 'Protects from scary spells and gazes for ' + str(round_to_1_dp(4/TIME_SCALAR)) + ' seconds'

class CounterSleep_CA(CounterspellParalysis_CA):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Counter: Sleep'

    def make_stuff_happen(self,target):
        self.c.current_stance = ST_COUNTER_SLEEP
        self.c.stance_wears_off = w.elapsed_since_beginning + 4
        
    def needed(self):
        for m in e.mobs:
            if m.alive and not m.about_to_die and m.action_in_progress is not None and m.action_in_progress.sleepy():
                if m.action_fires <= w.elapsed_since_beginning + 0.5:
                    return True
        return False

    def describe_resolution(self):
        return 'Protects from sleep-causing spells and gazes for ' + str(round_to_1_dp(4/TIME_SCALAR)) + ' seconds'

class CounterSilence_CA(CounterspellParalysis_CA):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Counter: Silence'

    def make_stuff_happen(self,target):
        self.c.current_stance = ST_COUNTER_SILENCE
        self.c.stance_wears_off = w.elapsed_since_beginning + 4
        
    def needed(self):
        for m in e.mobs:
            if m.alive and not m.about_to_die and m.action_in_progress is not None and m.action_in_progress.silencing():
                if m.action_fires <= w.elapsed_since_beginning + 0.5:
                    return True
        return False

    def describe_resolution(self):
        return 'Protects from silence-causing spells and gazes for ' + str(round_to_1_dp(4/TIME_SCALAR)) + ' seconds'

class ProtectTheFallen_CA(CounterspellParalysis_CA):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Protect The Fallen'

    def make_stuff_happen(self,target):
        self.c.current_stance = ST_COUNTER_DEATHBLOW
        self.c.stance_wears_off = w.elapsed_since_beginning + 4
        
    def needed(self):
        for m in e.mobs:
            if m.alive and not m.about_to_die and m.action_in_progress is not None and m.action_in_progress.finishing():
                if m.action_fires <= w.elapsed_since_beginning + 0.5:
                    return True
        return False

    def describe_resolution(self):
        return 'Protects a stunned, grappled or paralysed character from monsters with the Deathblow ability, for ' + str(round_to_1_dp(4/TIME_SCALAR)) + ' seconds'

class Reflection_CA(CounterspellParalysis_CA):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Reflection'

    def make_stuff_happen(self,target):
        self.c.current_stance = ST_COUNTER_BOLTS
        self.c.stance_wears_off = w.elapsed_since_beginning + 4
        
    def needed(self):
        for m in e.mobs:
            if m.alive and not m.about_to_die and m.action_in_progress is not None and m.action_in_progress.bolt():
                if m.action_fires <= w.elapsed_since_beginning + 0.5:
                    return True
        return False

    def describe_resolution(self):
        return 'Protects from firebolts and lightning bolts for ' + str(round_to_1_dp(4/TIME_SCALAR)) + ' seconds. Ineffective against dragon breath'

class Distraction_CA(CounterspellParalysis_CA):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Distraction'

    def cooldown(self):
        return 20.0

    def base_cost(self):
        return 10.0

    def make_stuff_happen(self,target):
        self.c.current_stance = ST_COUNTER_MANY
        self.c.stance_wears_off = w.elapsed_since_beginning + 4
        
    def needed(self):
        return False

    def describe_resolution(self):
        return 'Can protect the party from many kinds of special attacks, but is not reliable (especially against more powerful enemies). Lasts for ' + str(round_to_1_dp(4/TIME_SCALAR)) + ' seconds'

class StickyFlames_CA(Spell_CA):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Sticky Flames'

    def cooldown(self):
        return 10.0

    def message(self):
        return '' # 'Pchouf!'

    def target_enemy(self):
        return True

    def enemy_target_back_row_ok(self):
        return False

    def base_cost(self):
        return 2

    def duration(self):
        return 15.0

    def period(self):
        return 1.0
   
    def make_stuff_happen(self,target):
        d.sound_queue.append(S_GOOD_SPELL)
        if not target.fire_resistant:
            target.on_fire_until = w.elapsed_since_beginning + self.duration()
            target.fire_period = self.period()
            target.flash_image = I_FIRE
        else:
            d.message_queue.append(('Immune to fire!',target.image,COL_LIGHT))

    def describe_resolution(self):
        return 'Set a front row target of your choice on fire. The target takes one fire damage every ' + str(round_to_1_dp(self.period()/TIME_SCALAR)) + ' seconds for ' + str(round_to_1_dp(self.duration()/TIME_SCALAR)) + ' seconds. Armor is no protection'

class ShockingGrasp_CA(Spell_CA):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Shocking Grasp'

    def delay(self):
        return 1.0

    def cooldown(self):
        return 5.0

    def message(self):
        return '' # 'ZZZAPPP!'

    def target_enemy(self):
        return True

    def enemy_target_back_row_ok(self):
        return False

    def back_row_ok(self):
        return False

    def base_cost(self):
        return 2

    def min_damage(self):
        return 5

    def max_damage(self):
        return 15
    
    def make_stuff_happen(self,target):
        d.sound_queue.append(S_LIGHTNING_BOLT)
        target.take_damage(randint(self.min_damage(),self.max_damage()))
        target.flash_image = I_LIGHTNING

    def describe_resolution(self):
        return 'Do '+str(self.min_damage())+'-'+str(self.max_damage())+' damage to a front row target of your choice. Armor is no protection'

class LightningBolt_CA(Spell_CA):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Lightning Bolt'

    def delay(self):
        return 3.0

    def cooldown(self):
        return 25.0

    def message(self):
        return '' # 'ZZZAAAPPP!'

    def target_enemy(self):
        return True

    def enemy_target_back_row_ok(self):
        return False

    def base_cost(self):
        return 20

    def min_damage(self):
        return ((self.c.power+2) * 35) / 7

    def max_damage(self):
        return ((self.c.power+2) * 75) / 7
    
    def make_stuff_happen(self,target):
        if not target.lightning_resistant:
            d.sound_queue.append(S_LIGHTNING_BOLT)
            target.take_damage(randint(self.min_damage(),self.max_damage()))
            target.flash_image = I_LIGHTNING
        else:
            d.message_queue.append(('Immune to lightning!',target.image,COL_LIGHT))

    def describe_resolution(self):
        return 'Do '+str(self.min_damage())+'-'+str(self.max_damage())+' damage to a front row target of your choice. Armor is no protection. A very few monsters are lightning resistant and will take no damage.'

class Extinguish_CA(LightningBolt_CA):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Extinguish'

    def message(self):
        return '' # 'Be quenched!'
    
    def enemy_target_back_row_ok(self):
        return True
    
    def delay(self):
        return 0.5

    def cooldown(self):
        return 3.0

    def base_cost(self):
        return 7

    def min_damage(self):
        return 10

    def max_damage(self):
        return 30

    def ok(self):
        for m in e.mobs:
            if m.alive and not m.about_to_die and (m.fire_melee or (m.action_in_progress is not None and m.action_in_progress.fiery())):
                return Character_action.ok(self)
        return False
    
    def enemy_target_ok(self,m):
        return (m.fire_melee or (m.action_in_progress is not None and m.action_in_progress.fiery())) and Character_action.enemy_target_ok(self,m)

    def make_stuff_happen(self,target):
        if self.enemy_target_ok(target):
            d.sound_queue.append(S_GOOD_SPELL)
            target.flash_image = I_WATER
            d.sound_queue.append(S_EXTINGUISH)
            if target.fire_melee:
                if target.enrage > 0:
                    target.enrage = 0
                target.take_damage(randint(self.min_damage(),self.max_damage()))
            if (target.action_in_progress is not None and target.action_in_progress.fiery()):
                d.message_queue.append(('Action disrupted!',target.image,COL_LIGHT))
                target.action_in_progress = None

    def describe_resolution(self):
        return 'Do '+str(self.min_damage())+'-'+str(self.max_damage())+' damage to a fiery monster (such as an elemental or burning ghost, but not a dragon unfortunately). Armor is no protection. Can also be used to counter a fire-based attack used by a monster (such as firebolts or dragon breath), or quench an elemental\'s rage'

class Deconstruct_CA(LightningBolt_CA):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Demolish'

    def enemy_target_back_row_ok(self):
        return True
    
    def delay(self):
        return 2.0

    def cooldown(self):
        return 10.0

    def message(self):
        return '' # 'Return to your component parts!'

    def base_cost(self):
        return 15

    def min_damage(self):
        return 80

    def max_damage(self):
        return 120

    def ok(self):
        for m in e.mobs:
            if m.alive and not m.about_to_die and m.construct:
                return Character_action.ok(self)
        return False
    
    def enemy_target_ok(self,m):
        return m.construct and Character_action.enemy_target_ok(self,m)

    def make_stuff_happen(self,target):
        if self.enemy_target_ok(target):
            d.sound_queue.append(S_GOOD_SPELL)
            d.sound_queue.append(S_DECONSTRUCT)
            target.flash_image = I_DISMANTLE
            target.take_damage(randint(self.min_damage(),self.max_damage()))

    def describe_resolution(self):
        return 'Do '+str(self.min_damage())+'-'+str(self.max_damage())+' damage to a golem of your choice. Armor is no protection'

class CleansingFlame_CA(LightningBolt_CA):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Cleansing Flame'

    def cooldown(self):
        return 20.0

    def message(self):
        return 'BURN, INFIDEL!'

    def base_cost(self):
        return 20

    def min_damage(self):
        return 20
    
    def max_damage(self):
        return 60
    
    def make_stuff_happen(self,target):
        damage = randint(self.min_damage(),self.max_damage())
        if target.fire_resistant:
            damage = damage / 4
        target.take_damage(damage)
        target.flash_image = I_FIRE
        d.sound_queue.append(S_FIRE_BOLT)
  
    def describe_resolution(self):
        return 'Do '+str(self.min_damage())+'-'+str(self.max_damage())+' fire damage to a front row target of your choice. Armor is no protection, but some monsters are fire resistant'

class Spark_CA(LightningBolt_CA):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Spark'

    def delay(self):
        return 1.0

    def cooldown(self):
        return 10.0

    def message(self):
        return '' # 'ZAP!'

    def base_cost(self):
        return 5

    def min_damage(self):
        return ((self.c.power+2) * 5) / 7

    def max_damage(self):
        return ((self.c.power+2) * 30) / 7

class Firebolt_CA(CleansingFlame_CA):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Firebolt'

    def delay(self):
        return 2.0

    def cooldown(self):
        return 10.0
    
    def message(self):
        return '' # 'WHOOSH!'

    def base_cost(self):
        return 12

    def min_damage(self):
        return ((self.c.power+2) * 20) / 7

    def max_damage(self):
        return ((self.c.power+2) * 40) / 7
       
class Fly_CA(Spell_CA):
    def __init__(self):
        return Character_action.__init__(self)

    def base_cost(self):
        return 15

    def name(self):
        return 'Fly'

    def ok(self):
        if Character_action.ok(self):
            if not w.no_fly:
                for m in e.mobs:
                    if m.alive and not m.about_to_die and m.no_fly:
                        return False
                return True
        return False

    def make_stuff_happen(self,target):
        if self.ok():
            w.waiting_gold = 0
            for m in e.mobs:
                if m.alive and not m.about_to_die:
                    m.remove()
            self.c.current_stance = ST_FLY
            self.c.stance_wears_off = None
        else:
            d.message_queue.append(('You cannot fly now!',self.c.image,COL_LIGHT))
        
    def describe_resolution(self):
        return 'Whisks your party away from your enemies. You alight later in the dungeon, possibly bypassing enemies or traps. But be careful, some enemies cannot be escaped so easily.'

class Stealth_CA(Character_action):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Move Silently'

    def base_cost(self):
        return 10

    def ok(self):
        if Character_action.ok(self) and e.n_surviving_mobs()==0:
            return True
        return False

    def chance(self):
        return success_chance(self.c.cunning + 2)

    def make_stuff_happen(self,target):
        if self.ok():
            d.message_queue.append(('You fade into the shadows...',self.c.image,COL_LIGHT))
            self.c.current_stance = ST_STEALTH
            self.c.stance_wears_off = None
        else:
            d.message_queue.append(('You cannot sneak now!',self.c.image,COL_LIGHT))
        
    def describe_resolution(self):
        return 'Usable when no monsters are near. ' + str(self.chance()) + '% likely to succeed. If it does, you will evade the next group of monsters to arrive. Be careful - some enemies will be able to spot you no matter how hard you try to hide.'
  
class Berserk_CA(Character_action):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Berserk'

    def base_cost(self):
        return 20

    def back_row_ok(self):
        return False
    
    def make_stuff_happen(self,target):
        self.c.current_stance = ST_BERSERK
        self.c.stance_wears_off = w.elapsed_since_beginning + 15

    def describe_resolution(self):
        return 'Gives you +30% base damage and +3 attack speed and health regeneration - but you cannot use any abilities and your energy bar does not regenerate normally. Lasts for ' + str(round_to_1_dp(15/TIME_SCALAR)) + ' seconds'

class Ambush_CA(Bash_CA):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Ambush'

    def cooldown(self):
        return 10.0

    def make_stuff_happen(self,target):
        if target.seconds_since_arrived() > 2.5:
            d.message_queue.append(('Failed to ambush - too slow',self.c.image,COL_LIGHT))
        elif self.c.melee_hit_roll(target):
            d.sound_queue.append(self.c.melee_hit_sound)
            d.message_queue.append(('STAB!',self.c.image,COL_LIGHT))
            target.take_damage(max(0,int(round(2 * self.c.melee_damage_roll(target)))-target.current_armor()))
        else:
            d.sound_queue.append(self.c.melee_miss_sound)
            d.message_queue.append(('Missed!',self.c.image,COL_LIGHT))

    def describe_resolution(self):
        return 'Use Ambush immediately when a new enemy appears, for a double damage attack. Both you and the enemy must be in the front row.'

    def reflex_level(self):
        return 5
    
    def needed(self):
        for m in e.mobs:
            if m.alive and (not m.about_to_die) and m.front_row():
                if m.seconds_since_arrived() < 1 and m.seconds_since_arrived() > 0.5 and m.max_health > self.c.melee_damage_roll(None):
                    return True
        return False

    def reflex_target(self):
        for m in e.mobs:
            if m.alive and (not m.about_to_die) and m.front_row():
                if m.seconds_since_arrived() < 1:
                    return m
        return None

    def describe_reflex_condition(self):
        return 'an enemy has just arrived'

class Weaken_CA(Bash_CA):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Weaken'

    def cooldown(self):
        return 10.0

    def base_cost(self):
        return 3    

    def make_stuff_happen(self,target):
        if self.c.melee_hit_roll(target):
            d.sound_queue.append(self.c.melee_hit_sound)
            damage = int(round(self.c.melee_damage_roll(target)))
            if damage > target.current_armor():
                target.take_damage(damage)
                if target.alive and not target.about_to_die:
                    if not target.poison_resistant:
                        target.poisoned_until = w.elapsed_since_beginning + self.duration()
                        target.poison_period = self.period()
                        d.message_queue.append(('STAB!',self.c.image,COL_LIGHT))
                    else:
                        d.message_queue.append(('Target was immune to poison...',self.c.image,COL_LIGHT))
            else:
                d.sound_queue.append(self.c.melee_miss_sound)
                d.message_queue.append(('Could not penetrate armor...',self.c.image,COL_LIGHT))                       
        else:
            d.sound_queue.append(self.c.melee_miss_sound)
            d.message_queue.append(('Missed!',self.c.image,COL_LIGHT))

    def duration(self):
        return 10.0

    def period(self):
        return 0.5
              
    def describe_resolution(self):
        return 'Choose a front row target to attack. If you hit and penetrate armor, the target is poisoned and takes one point of damage every ' + str(round_to_1_dp(self.period()/TIME_SCALAR)) + ' seconds for ' + str(round_to_1_dp(self.duration()/TIME_SCALAR)) + ' seconds. Some foes are resistant to poison...'
    
    def needed(self):
        return False

class BloodMagic_CA(Spell_CA):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Blood Magic'

    def cooldown(self):
        return 5.0
    
    def ok(self):
        return Character_action.ok(self) and self.c.health > 12

    def make_stuff_happen(self,target):
        d.sound_queue.append(S_GOOD_SPELL)
        self.c.health = self.c.health - 12
        recover = min(10,self.c.max_energy-self.c.energy)
        self.c.energy = self.c.energy + recover
        d.log('Converts 12 health to ' + str(recover) + ' bar')

    def describe_resolution(self):
        return 'Convert 12 health to 10 bar'

class Escape_CA(Spell_CA):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Word of Recall'
    
    def base_cost(self):
        return 5

    def make_stuff_happen(self,target):
        self.c.flee()
        d.log('Flees')

    def describe_resolution(self):
        return 'Transports you instantaneously to a safe place far away'

    def reflex_level(self):
        return 5
    
    def needed(self):
        if self.c.health < 0.2 * self.c.max_health:
            return True
        return False

    def describe_reflex_condition(self):
        return 'your health drops below 10% of maximum'   

class Vendetta_CA(Character_action):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Vendetta'

    def target_enemy(self):
        return True

    def base_cost(self):
        return 3

    def make_stuff_happen(self,target):
        target.vendetta = True
        d.message_queue.append(('Marked for death!',target.image,COL_LIGHT))

    def describe_resolution(self):
        return 'Declare a vendetta against one of your enemies. Your party will concentrate their effects on that enemy until it is dead or fled'

class Knockback_CA(Bash_CA):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Knockback'

    def base_cost(self):
        return 4

    def damage_mult(self):
        return 1

    def needed(self):
        return False

    def make_stuff_happen(self,target):
        if self.c.melee_hit_roll(target):
            d.sound_queue.append(S_SMITE)
            #d.message_queue.append(('THUMP!',self.c.image,COL_LIGHT))
            target.take_damage(max(0,int(round(self.damage_mult() * self.c.melee_damage_roll(target)))-target.current_armor()))
            if target.alive and not target.about_to_die:
                if not target.stun_resistant:
                    if randint(1,100) <= success_chance(4 + self.c.strength - target.max_health/50):                     
                        if e.n_surviving_front_mobs()==1 or e.last_free_back_pos() is None:
                            pushed_forward = e.random_back_target(None)
                            if pushed_forward is not None:
                                pushed_forward.slide_from = pushed_forward.pos
                                pushed_forward.pos = target.pos
                                pushed_forward.slide_to = target.pos
                                pushed_forward.slide_start = w.elapsed_since_beginning
                                pushed_forward.slide_finish = w.elapsed_since_beginning + 0.1
                        target.slide_from = target.pos
                        target.slide_start = w.elapsed_since_beginning
                        target.slide_finish = w.elapsed_since_beginning + 0.1
                        if target.pos==POS_M_FRONT_1 and e.get_monster_at_pos(POS_M_BACK_1) is None:
                            target.pos=POS_M_BACK_1
                        elif target.pos==POS_M_FRONT_2 and e.get_monster_at_pos(POS_M_BACK_2) is None:
                            target.pos=POS_M_BACK_2
                        elif target.pos==POS_M_FRONT_3 and e.get_monster_at_pos(POS_M_BACK_3) is None:
                            target.pos=POS_M_BACK_3
                        else:
                            target.pos = e.last_free_back_pos()
                        target.slide_to = target.pos
                        target.move_to_front_at = w.elapsed_since_beginning + 5.0
                    else:
                        d.message_queue.append(('Stands fast',target.image,COL_LIGHT))
                else:
                    d.message_queue.append(('Immune to knockback',target.image,COL_LIGHT))
        else:
            d.sound_queue.append(self.c.melee_miss_sound)
            d.message_queue.append(('Missed!',self.c.image,COL_LIGHT))

    def describe_resolution(self):
        return 'Attack a front row target of your choice for normal damage, and push it back into the back row. More likely to work if your Strength is high and the monster is small. Stun resistant foes are immune'

class StunningBlow_CA(Bash_CA):
    def __init__(self):
        return Character_action.__init__(self)

    def name(self):
        return 'Stunning Blow'

    def base_cost(self):
        return 4

    def damage_mult(self):
        return 1

    def needed(self):
        return False
    
    def make_stuff_happen(self,target):
        if self.c.melee_hit_roll(target):
            d.sound_queue.append(S_SMITE)
            #d.message_queue.append(('THUMP!',self.c.image,COL_LIGHT))
            target.take_damage(max(0,int(round(self.damage_mult() * self.c.melee_damage_roll(target)))-target.current_armor()))
            if target.alive and not target.about_to_die and not target.stun_resistant:
                target.stunned = True
                d.message_queue.append(('Stunned',target.image,COL_LIGHT))
                duration = randint(2,8) * (2.0 / (2 + (target.max_health / 50.0)))
                if target.dupe_until >= w.elapsed_since_beginning:
                    duration = duration * 2
                target.stunned_until = w.elapsed_since_beginning + duration
                target.action_in_progress = None
        else:
            d.sound_queue.append(self.c.melee_miss_sound)
            d.message_queue.append(('Missed!',self.c.image,COL_LIGHT))

    def describe_resolution(self):
        return 'Attack a front row target of your choice for normal damage, and stun it for a few seconds (unless it is stun resistant). Big monsters will be stunned for a shorter time than little ones'
       
def get_action(action_code):
    if action_code==CA_DEFLECT:
        return Deflect_CA()
    elif action_code==CA_SMITE:
        return Smite_CA()
    elif action_code==CA_LEADER:
        return Leader_CA()
    elif action_code==CA_RETRENCH:
        return Retrench_CA()
    elif action_code==CA_CLEAVE:
        return Cleave_CA()
    elif action_code==CA_BLOCK:
        return Block_CA()
    elif action_code==CA_BASH:
        return Bash_CA()
    elif action_code==CA_SWIPE:
        return Swipe_CA()
    elif action_code==CA_RUTHLESS:
        return Ruthless_CA()
    elif action_code==CA_THROW:
        return Throw_CA()
    elif action_code==CA_POISON_RESISTANCE:
        return PoisonResistance_CA()
    elif action_code==CA_FIRE_RESISTANCE:
        return FireResistance_CA()
    elif action_code==CA_AGGRESSIVE:
        return Aggressive_CA()
    elif action_code==CA_TAUNT:
        return Taunt_CA()
    elif action_code==CA_DRAGON_SLAYER:
        return DragonSlayer_CA()
    elif action_code==CA_DEATHBLOW:
        return Deathblow_CA()
    elif action_code==CA_RESIST_FIRE:
        return ResistFire_CA()
    elif action_code==CA_SACRIFICE:
        return Sacrifice_CA()
    elif action_code==CA_HEALING:
        return Healing_CA()
    elif action_code==CA_SELF_HEAL:
        return SelfHeal_CA()
    elif action_code==CA_SMALL_HEAL:
        return SmallHeal_CA()
    elif action_code==CA_BIG_HEAL:
        return BigHeal_CA()
    elif action_code==CA_MASS_HEAL:
        return MassHeal_CA()
    elif action_code==CA_CURE_POISON:
        return CurePoison_CA()
    elif action_code==CA_ROSARY:
        return Rosary_CA()
    elif action_code==CA_AGGRO:
        return Aggro_CA()
    elif action_code==CA_ARMOR:
        return Armor_CA()
    elif action_code==CA_CONCENTRATION:
        return Concentration_CA()
    elif action_code==CA_HEALER:
        return Healer_CA()
    elif action_code==CA_RIGHTEOUSNESS:
        return Righteousness_CA()
    elif action_code==CA_LUCKY_ESCAPE:
        return LuckyEscape_CA()
    elif action_code==CA_DODGE:
        return Dodge_CA()
    elif action_code==CA_SNIPE:
        return Snipe_CA()
    elif action_code==CA_TAKE_COVER:
        return TakeCover_CA()
    elif action_code==CA_LUCKY_SHOT:
        return LuckyShot_CA()
    elif action_code==CA_FIND_WEAKNESS:
        return FindWeakness_CA()
    elif action_code==CA_FIND_TRAPS:
        return FindTraps_CA()
    elif action_code==CA_INSULT_YOUR_MOTHER:
        return InsultYourMother_CA()
    elif action_code==CA_ASSASSIN:
        return Assassin_CA()
    elif action_code==CA_ARCHER:
        return Archer_CA()
    elif action_code==CA_RIPOSTE:
        return Riposte_CA()
    elif action_code==CA_PENETRATION:
        return Penetration_CA()
    elif action_code==CA_MORE_GOLD:
        return MoreGold_CA()
    elif action_code==CA_MORE_GEMS:
        return MoreGems_CA()
    elif action_code==CA_PICK_LOCKS:
        return PickLocks_CA()
    elif action_code==CA_INCONSPICUOUS:
        return Inconspicuous_CA()
    elif action_code==CA_POISON_CLOUD:
        return PoisonousCloud_CA()
    elif action_code==CA_COUNTERSPELL_PARALYSIS:
        return CounterspellParalysis_CA()
    elif action_code==CA_LIGHTNING_BOLT:
        return LightningBolt_CA()
    elif action_code==CA_FRONT_ROW_BUFF:
        return FrontRowBuff_CA()
    elif action_code==CA_CLEANSING_FLAME:
        return CleansingFlame_CA()
    elif action_code==CA_REVEAL_DANGER:
        return RevealDanger_CA()
    elif action_code==CA_ALL_OUT_DEFENSE:
        return AllOutDefense_CA()
    elif action_code==CA_REVITALISE:
        return Revitalise_CA()
    elif action_code==CA_STEALTH:
        return Stealth_CA()
    elif action_code==CA_FLY:
        return Fly_CA()
    elif action_code==CA_BERSERK:
        return Berserk_CA()
    elif action_code==CA_AMBUSH:
        return Ambush_CA()
    elif action_code==CA_WAND_LORE:
        return WandLore_CA()
    elif action_code==CA_WAND_LORE_2:
        return WandLore2_CA()
    elif action_code==CA_BUFF_EV:
        return BuffEV_CA()
    elif action_code==CA_FORCE_BRAND:
        return ForceBrand_CA()
    elif action_code==CA_SHIELD:
        return Shield_CA()
    elif action_code==CA_INVULNERABILITY:
        return Invulnerability_CA()
    elif action_code==CA_DEFLECT_MISSILE:
        return DeflectMissile_CA()
    elif action_code==CA_POISON_CLOUD_2:
        return PoisonCloud2_CA()
    elif action_code==CA_SPARK:
        return Spark_CA()
    elif action_code==CA_FIREBOLT:
        return Firebolt_CA()
    elif action_code==CA_MISSILE:
        return Missile_CA()
    elif action_code==CA_ESCAPE:
        return Escape_CA()
    elif action_code==CA_KNIT_FLESH:
        return KnitFlesh_CA()
    elif action_code==CA_BLOOD_MAGIC:
        return BloodMagic_CA()
    elif action_code==CA_STICKY_FLAMES:
        return StickyFlames_CA()
    elif action_code==CA_SHOCKING_GRASP:
        return ShockingGrasp_CA()
    elif action_code==CA_WEAKEN:
        return Weaken_CA()
    elif action_code==CA_ACCURATE_SHOT:
        return AccurateShot_CA()
    elif action_code==CA_STEELY_GAZE:
        return SteelyGaze_CA()  
    elif action_code==CA_RESIST_FEAR:
        return ResistFear_CA()
    elif action_code==CA_KNOCKBACK:
        return Knockback_CA()
    elif action_code==CA_VETERAN:
        return Veteran_CA()
    elif action_code==CA_PROTECT_THE_FALLEN:
        return ProtectTheFallen_CA()
    elif action_code==CA_STUNNING_BLOW:
        return StunningBlow_CA()
    elif action_code==CA_RAGE:
        return Rage_CA()
    elif action_code==CA_CURE_FEAR:
        return CureFear_CA()
    elif action_code==CA_UNDEAD_SLAYER:
        return UndeadSlayer_CA()
    elif action_code==CA_TURN_UNDEAD:
        return TurnUndead_CA()
    elif action_code==CA_MEDITATION:
        return Meditation_CA()
    elif action_code==CA_CURE_MORTAL_WOUNDS:
        return CureMortalWounds_CA()
    elif action_code==CA_CURSE:
        return Curse_CA()
    elif action_code==CA_MAGEBANE:
        return Magebane_CA()
    elif action_code==CA_VENDETTA:
        return Vendetta_CA()
    elif action_code==CA_CONFUSING_SPIEL:
        return ConfusingSpiel_CA()
    elif action_code==CA_FREE_ACTION:
        return FreeAction_CA()
    elif action_code==CA_DISTRACTION:
        return Distraction_CA()
    elif action_code==CA_CONFUSION:
        return Confusion_CA()
    elif action_code==CA_DECONSTRUCT:
        return Deconstruct_CA()
    elif action_code==CA_COUNTER_FEAR:
        return CounterFear_CA()
    elif action_code==CA_COUNTER_SLEEP:
        return CounterSleep_CA()
    elif action_code==CA_COUNTER_SILENCE:
        return CounterSilence_CA()
    elif action_code==CA_REFLECTION:
        return Reflection_CA()
    elif action_code==CA_EXTINGUISH:
        return Extinguish_CA()
    elif action_code==CA_SLOW_TIME:
        return SlowTime_CA()
    elif action_code==CA_SHOCKWAVE:
        return Shockwave_CA()
    elif action_code==CA_DISPELL:
        return Dispell_CA()
    elif action_code==CA_EPIC_BLOCK:
        return Epic_Block_CA()
    elif action_code==CA_LEAP_ASIDE:
        return Leap_Aside_CA()
    else:
        return None

class Character:
    def __init__(self,charsheet,number,pos):
        self.number = number
        self.pos = pos
        # start looking at charsheet
        self.level = charsheet.level
        self.name = charsheet.name
        self.class_code = charsheet.class_code
        self.image = charsheet.image_code
        self.energy_gain_period = None
        self.energy_for_healing = False
        self.energy_for_gold = False
        self.energy_for_evil_kill = False
        self.energy_for_shot = False
        self.energy_for_hit = False
        self.energy_for_getting_hit = False
        self.concentration = False
        self.auto_retrench = False
        self.aggro = charsheet.get_aggro()
        self.strength = None
        self.skill = None
        self.valor = None
        self.wisdom = None
        self.daring = None
        self.cunning = None
        self.power = None
        self.subtlety = None
        if self.class_code==C_WARRIOR:
            self.strength = charsheet.get_strength()
            self.skill = charsheet.get_skill()
            self.energy_gain_period = 2
        elif self.class_code==C_PRIEST:
            self.valor = charsheet.get_valor()
            self.wisdom = charsheet.get_wisdom()
            self.energy_for_evil_kill = True
        elif self.class_code==C_ROGUE:
            self.daring = charsheet.get_daring()
            self.cunning = charsheet.get_cunning()
            self.energy_for_gold = True
        elif self.class_code==C_MAGE:
            self.power = charsheet.get_power()
            self.subtlety = charsheet.get_subtlety()
            self.energy_gain_period = 6             
        self.learning = charsheet.intelligence
        self.constitution = charsheet.get_constitution()
        self.reflexes = charsheet.get_reflexes()
        self.stamina = charsheet.get_stamina()
        self.melee_attack = True
        self.speed = charsheet.get_speed()
        self.damage = charsheet.get_damage()
        self.accuracy = charsheet.get_accuracy()
        if charsheet.melee_weapon is not None:
            self.melee_dice = charsheet.melee_weapon.base_damage_dice()
            self.melee_sides = charsheet.melee_weapon.base_damage_sides()
            self.melee_hit_sound = charsheet.melee_weapon.hit_message()
            self.melee_miss_sound = charsheet.melee_weapon.miss_message()
        else:
            self.melee_dice = 1
            self.melee_sides = 4
            self.melee_hit_sound = S_HIT
            self.melee_miss_sound = S_MISS
        self.poison_melee = charsheet.poison_melee()
        self.fire_melee = charsheet.fire_melee()
        self.dragon_slaying_melee = False
        self.undead_slaying_melee = False
        self.magebane_missile = False
        self.missile_attack = False
        if charsheet.missile_weapon is not None:
            self.missile_attack = True
            self.rapidity = charsheet.get_rapidity()
            self.missile_dice = charsheet.missile_weapon.base_damage_dice()
            self.missile_sides = charsheet.missile_weapon.base_damage_sides()
            self.penetration = charsheet.get_penetration()
            self.aim = charsheet.get_aim()
            self.missile_hit_sound = charsheet.missile_weapon.hit_message()
            self.missile_miss_sound = charsheet.missile_weapon.miss_message()
            self.poison_missile = charsheet.poison_missile()
            self.fire_missile = charsheet.fire_missile()
        self.armor = charsheet.get_armor()
        self.evasion = charsheet.get_evasion()
        self.actions = []
        potion_codes = [POT_SMALLHEAL,POT_MEDHEAL,POT_BIGHEAL,POT_BAR_PRIEST,POT_BAR_MAGE,POT_BAR_ROGUE,POT_PERCENTHEAL,POT_ANTIDOTE,POT_FIRE_RESIST,POT_COLD_RESIST,POT_INVULNERABILITY,POT_COURAGE,POT_BERSERK]
        potion_actions = [PotSmallheal_CA,PotMedheal_CA,PotBigheal_CA,PotBarpriest_CA,PotBarmage_CA,PotBarrogue_CA,PotPercentheal_CA,PotAntidote_CA,PotFireresist_CA,PotColdresist_CA,PotInvulnerability_CA,PotCourage_CA,PotBerserk_CA]
        for i in range(len(potion_codes)):
            code = potion_codes[i]
            action = potion_actions[i]
            n_of = 0
            for j in charsheet.potions:
                if j==code:
                    n_of = n_of + 1
            if n_of>0:
                self.add_action(action(n_of),None) 
        for action_code in charsheet.get_action_codes():
            a = get_action(action_code)
            if a is not None:
                self.add_action(a,action_code)
            if action_code==CA_DRAGON_SLAYER:
                self.dragon_slaying_melee = True
            if action_code==CA_ARMOR:
                self.armor = self.armor + 1
            if action_code==CA_AGGRO:
                self.aggro = self.aggro + 1
            if action_code==CA_ROSARY:
                self.energy_gain_period = 2
                self.energy_for_evil_kill = False                
            if action_code==CA_HEALER:
                self.energy_for_healing = True
                self.energy_for_evil_kill = False
            if action_code==CA_ARCHER:
                self.energy_for_gold = False
                self.energy_for_shot = True
            if action_code==CA_ASSASSIN:
                self.energy_for_gold = False
                self.energy_for_hit = True
            if action_code==CA_PENETRATION:
                self.penetration = self.penetration + 1
            if action_code==CA_INCONSPICUOUS:
                self.evasion = self.evasion + 1
                self.aggro = self.aggro - 1
            if action_code==CA_WAND_LORE:
                self.aim = self.aim + 1
                self.rapidity = self.rapidity + 1
            if action_code==CA_WAND_LORE_2:
                self.aim = self.aim + 2
                self.rapidity = self.rapidity + 2
            if action_code==CA_CONCENTRATION:
                self.concentration = True
            if action_code==CA_UNDEAD_SLAYER:
                self.undead_slaying_melee = True
            if action_code==CA_MAGEBANE:
                self.magebane_missile = True
            if action_code==CA_RAGE:
                self.energy_for_getting_hit = True
            if action_code==CA_VETERAN:
                self.auto_retrench = True
        # finished looking at charsheet
        self.alive = True
        self.about_to_die = False
        self.fled = False
        self.max_health = (self.constitution + 3) * (self.level + 3)
        self.health = self.max_health
        self.max_energy = 15 + 5 * self.level
        self.xp = 0
        self.energy = self.max_energy
        self.action_in_progress = None
        self.last_energy_gain = 0
        self.dumb_until = 0
        self.current_stance = None
        self.stance_wears_off = 0
        self.counter_against = None
        self.status_effects = {} # EF_ code to Effect_parameters
        self.flash_image = None
        self.flash_starts = None
        self.flash_number = None
        self.number_starts = None
        self.number_color = None
        self.grappled_by = None
        self.slide_from = None
        self.slide_to = None
        self.slide_start = 0
        self.slide_finish = 0
  
    def add_action(self,action,action_code):
        action.c = self
        action.action_code = action_code
        self.actions.append(action)
    
    def get_chardump(self):
        dump = []
        dump.append(self.name.upper())
        dump.append('Level ' + str(self.level) + ' ' + class_code_to_name[self.class_code])
        dump.append('--Status--')
        dump.append('Health ' + str(self.health) + ' / ' + str(self.max_health))
        dump.append('Bar    ' + str(self.energy) + ' / ' + str(self.max_energy))
        if self.current_stance==ST_BERSERK:
            dump.append('Berserk')
        elif self.current_stance==ST_INVULNERABLE:
            dump.append('Invulnerable')
        elif self.current_stance==ST_EPIC_BLOCK:
            dump.append('Epic Blocking')
        elif self.current_stance==ST_LEAP_ASIDE:
            dump.append('Leaping Aside')
        elif self.current_stance==ST_BLOCK:
            dump.append('Blocking')
        elif self.current_stance==ST_RIPOSTE:
            dump.append('Riposting')
        elif self.current_stance==ST_DEFLECT:
            dump.append('Deflecting')
        elif self.current_stance==ST_RETRENCH:
            dump.append('Retrenched')
        elif self.current_stance==ST_ALL_OUT_DEFENSE:
            dump.append('Defending')
        elif self.current_stance==ST_COUNTER_PARALYSIS:
            dump.append('Has used: Counter Paralysis')
        elif self.current_stance==ST_COUNTER_FEAR:
            dump.append('Has used: Counter Fear')
        elif self.current_stance==ST_COUNTER_SLEEP:
            dump.append('Has used: Counter Sleep')
        elif self.current_stance==ST_COUNTER_SILENCE:
            dump.append('Has used: Counter Silence')
        elif self.current_stance==ST_COUNTER_BOLTS:
            dump.append('Has used: Reflection')
        elif self.current_stance==ST_COUNTER_DEATHBLOW:
            dump.append('Has used: Protect The Fallen')
        elif self.current_stance==ST_COUNTER_MANY:
            dump.append('Has used: Distraction')
        elif self.current_stance==ST_TAKE_COVER:
            dump.append('In cover')
        if self.has_status_effect(EF_DODGE):
            dump.append('Dodging')
        if self.has_status_effect(EF_MEDITATION):
            dump.append('Meditating')
        if self.has_status_effect(EF_EV):
            dump.append('Warded')
        if self.has_status_effect(EF_FORCEBRAND):
            dump.append('Force Brand')
        if self.has_status_effect(EF_SHIELD):
            dump.append('Shielded')
        if self.has_status_effect(EF_REVEAL_DANGER):
            dump.append('Find Traps')
        if self.has_status_effect(EF_FRONTROWBUFF):
            dump.append('Exhorted')
        if self.has_status_effect(EF_RIGHTEOUSNESS):
            dump.append('Righteous')
        if self.has_status_effect(EF_TAUNT):
            dump.append('Taunting')
        if self.has_status_effect(EF_INSULT):
            dump.append('Insulting')
        if self.has_status_effect(EF_FIND_WEAKNESS):
            dump.append('Find Weakness')
        if self.has_status_effect(EF_RESIST_FIRE):
            dump.append('Fire resistant')
        if self.has_status_effect(EF_RESIST_COLD):
            dump.append('Cold resistant')
        if self.has_status_effect(EF_RESIST_POISON):
            dump.append('Poison resistant')
        if self.has_status_effect(EF_PARALYSIS):
            dump.append('Paralysed')
        if self.has_status_effect(EF_POISON):
            dump.append('Poisoned')
        if self.has_status_effect(EF_SLEEP):
            dump.append('Asleep')
        if self.has_status_effect(EF_STUN):
            dump.append('Stunned')
        if self.has_status_effect(EF_GRAPPLE):
            dump.append('Grappled')
        if self.has_status_effect(EF_FEAR):
            dump.append('Afraid')
        if self.has_status_effect(EF_CURSE):
            dump.append('Cursed')
        dump.append('--Stats--')
        if self.class_code==C_WARRIOR:
            dump.append('Strength     ' + str(self.strength))
            dump.append('Skill        ' + str(self.skill))
        elif self.class_code==C_PRIEST:
            dump.append('Valor        ' + str(self.valor))
            dump.append('Wisdom       ' + str(self.wisdom))
        elif self.class_code==C_ROGUE:
            dump.append('Daring       ' + str(self.daring))
            dump.append('Cunning      ' + str(self.cunning))
        elif self.class_code==C_MAGE:
            dump.append('Power        ' + str(self.power))
            dump.append('Subtlety     ' + str(self.subtlety))
        dump.append('Constitution ' + str(self.constitution))
        dump.append('Stamina      ' + str(self.stamina))
        dump.append('Reflexes     ' + str(self.reflexes))
        dump.append('--Defenses--')
        dump.append('Armor   ' + str(self.current_armor()))
        dump.append('Evasion ' + str(self.current_evasion()))
        dump.append('Aggro   ' + str(self.current_aggro()))
        if self.melee_attack:
            dump.append('--Base melee attack--')
            if self.fire_melee:
                dump.append('Fire attack (less effective against fire-resistant monsters)')
            if self.poison_melee:
                dump.append('Poison attack (less effective against poison-resistant monsters)')
            if self.dragon_slaying_melee:
                dump.append('Add +100% base damage against dragons')
            if self.undead_slaying_melee:
                dump.append('Add +100% base damage against undead')
            dump.append('Accuracy ' + str(self.current_accuracy()))
            dump.append('Rate of 1 per ' + str(round_to_1_dp(self.current_melee_period()/TIME_SCALAR)) + ' s')
            dump.append('Damage ' + str(self.melee_dice) + 'd' + str(self.melee_sides) + ' + ' + str(10*self.current_damage()) + '%')
            dump.append('DPS vs armor 0 evasion 0: '+ str(round_to_2_dp(self.melee_dps_vs(0,0))))
            dump.append('DPS vs armor 3 evasion 3:   '+ str(round_to_2_dp(self.melee_dps_vs(3,3))))
            dump.append('DPS vs armor 6 evasion 0:   '+ str(round_to_2_dp(self.melee_dps_vs(6,0))))
            dump.append('DPS vs armor 0 evasion 6:   '+ str(round_to_2_dp(self.melee_dps_vs(0,6))))
        if self.missile_attack:
            dump.append('--Base missile attack--')
            if self.fire_missile:
                dump.append('Fire attack (less effective against fire-resistant monsters)')
            if self.poison_missile:
                dump.append('Poison attack (less effective against poison-resistant monsters)')
            dump.append('Aim ' + str(self.current_aim()))
            dump.append('Rate of 1 per ' + str(round_to_1_dp(self.current_missile_period()/TIME_SCALAR)) + ' s')
            dump.append('Damage ' + str(self.missile_dice) + 'd' + str(self.missile_sides) + ' + ' + str(10*self.current_penetration()) + '%')        
            dump.append('DPS vs armor 0 evasion 0: '+ str(round_to_2_dp(self.missile_dps_vs(0,0))))
            dump.append('DPS vs armor 3 evasion 3:   '+ str(round_to_2_dp(self.missile_dps_vs(3,3))))
            dump.append('DPS vs armor 6 evasion 0:   '+ str(round_to_2_dp(self.missile_dps_vs(6,0))))
            dump.append('DPS vs armor 0 evasion 6:   '+ str(round_to_2_dp(self.missile_dps_vs(0,6))))
        dump.append('--Abilities--')
        for action in self.actions:
            for line in action.get_description():
                dump.append(' '+line)
        return dump
        
    def get_container_chardump(self):
        text_style = {'font': pygame.font.Font("files/FreeSans.ttf",     20), 'padding_right': 10}
        bold_style = {'font': pygame.font.Font("files/FreeSansBold.ttf", 20), 'padding_right': 10}
        italic_style = {'font': pygame.font.Font("files/FreeSansOblique.ttf", 20), 'padding_right': 10}
        dump = gui.Table()
        dump.tr()
        dump.td(gui.Spacer(height=20,width=280),colspan=2)
        dump.tr()
        dump.td(gui.Label("Class:", style=text_style), align=-1)
        dump.td(gui.Label(class_code_to_name[self.class_code], style=text_style), align=-1)
        dump.tr()
        dump.td(gui.Label("Level:", style=text_style), align=-1)
        dump.td(gui.Label(str(self.level), style=text_style), align=-1, colspan=3)
        dump.tr()
        dump.td(gui.Spacer(height=20,width=280),colspan=2)
        dump.tr()
        dump.td(gui.Label("Status", style=bold_style), align=-1,colspan=2)
        dump.tr()
        dump.td(gui.Label("Health:", style=text_style), align=-1)
        dump.td(gui.Label(str(self.health) + ' / ' + str(self.max_health), style=text_style), align=-1)
        dump.tr()
        dump.td(gui.Label("Bar:", style=text_style), align=-1)
        dump.td(gui.Label(str(self.energy) + ' / ' + str(self.max_energy), style=text_style), align=-1)
        dump.tr()
        if self.current_stance==ST_BERSERK:
            dump.td(gui.Label('Berserk', style=italic_style), align=-1, colspan=2)
            dump.tr()
        elif self.current_stance==ST_INVULNERABLE:
            dump.td(gui.Label('Invulnerable', style=italic_style), align=-1, colspan=2)
            dump.tr()
        elif self.current_stance==ST_EPIC_BLOCK:
            dump.td(gui.Label('Epic Blocking', style=italic_style), align=-1, colspan=2)
            dump.tr()
        elif self.current_stance==ST_LEAP_ASIDE:
            dump.td(gui.Label('Leaping Aside', style=italic_style), align=-1, colspan=2)
            dump.tr()
        elif self.current_stance==ST_BLOCK:
            dump.td(gui.Label('Blocking', style=italic_style), align=-1, colspan=2)
            dump.tr()
        elif self.current_stance==ST_RIPOSTE:
            dump.td(gui.Label('Riposting', style=italic_style), align=-1, colspan=2)
            dump.tr()
        elif self.current_stance==ST_DEFLECT:
            dump.td(gui.Label('Deflecting', style=italic_style), align=-1, colspan=2)
            dump.tr()
        elif self.current_stance==ST_RETRENCH:
            dump.td(gui.Label('Retrenched', style=italic_style), align=-1, colspan=2)
            dump.tr()
        elif self.current_stance==ST_ALL_OUT_DEFENSE:
            dump.td(gui.Label('Defending', style=italic_style), align=-1, colspan=2)
            dump.tr()
        elif self.current_stance==ST_COUNTER_PARALYSIS:
            dump.td(gui.Label('Has used: Counter Paralysis', style=italic_style), align=-1, colspan=2)
            dump.tr()
        elif self.current_stance==ST_COUNTER_FEAR:
            dump.td(gui.Label('Has used: Counter Fear', style=italic_style), align=-1, colspan=2)
            dump.tr()
        elif self.current_stance==ST_COUNTER_SLEEP:
            dump.td(gui.Label('Has used: Counter Sleep', style=italic_style), align=-1, colspan=2)
            dump.tr()
        elif self.current_stance==ST_COUNTER_SILENCE:
            dump.td(gui.Label('Has used: Counter Silence', style=italic_style), align=-1, colspan=2)
            dump.tr()
        elif self.current_stance==ST_COUNTER_BOLTS:
            dump.td(gui.Label('Has used: Reflection', style=italic_style), align=-1, colspan=2)
            dump.tr()
        elif self.current_stance==ST_COUNTER_DEATHBLOW:
            dump.td(gui.Label('Has used: Protect The Fallen', style=italic_style), align=-1, colspan=2)
            dump.tr()
        elif self.current_stance==ST_COUNTER_MANY:
            dump.td(gui.Label('Has used: Distraction', style=italic_style), align=-1, colspan=2)
            dump.tr()
        elif self.current_stance==ST_TAKE_COVER:
            dump.td(gui.Label('In cover', style=italic_style), align=-1, colspan=2)
            dump.tr()
        if self.has_status_effect(EF_DODGE):
            dump.td(gui.Label('Dodging', style=italic_style), align=-1, colspan=2)
            dump.tr()
        if self.has_status_effect(EF_MEDITATION):
            dump.td(gui.Label('Meditating', style=italic_style), align=-1, colspan=2)
            dump.tr()
        if self.has_status_effect(EF_EV):
            dump.td(gui.Label('Warded', style=italic_style), align=-1, colspan=2)
            dump.tr()
        if self.has_status_effect(EF_FORCEBRAND):
            dump.td(gui.Label('Force Brand', style=italic_style), align=-1, colspan=2)
            dump.tr()
        if self.has_status_effect(EF_SHIELD):
            dump.td(gui.Label('Shielded', style=italic_style), align=-1, colspan=2)
            dump.tr()
        if self.has_status_effect(EF_REVEAL_DANGER):
            dump.td(gui.Label('Find Traps', style=italic_style), align=-1, colspan=2)
            dump.tr()
        if self.has_status_effect(EF_FRONTROWBUFF):
            dump.td(gui.Label('Exhorted', style=italic_style), align=-1, colspan=2)
            dump.tr()
        if self.has_status_effect(EF_RIGHTEOUSNESS):
            dump.td(gui.Label('Righteous', style=italic_style), align=-1, colspan=2)
            dump.tr()
        if self.has_status_effect(EF_TAUNT):
            dump.td(gui.Label('Taunting', style=italic_style), align=-1, colspan=2)
            dump.tr()
        if self.has_status_effect(EF_INSULT):
            dump.td(gui.Label('Insulting', style=italic_style), align=-1, colspan=2)
            dump.tr()
        if self.has_status_effect(EF_FIND_WEAKNESS):
            dump.td(gui.Label('Find Weakness', style=italic_style), align=-1, colspan=2)
            dump.tr()
        if self.has_status_effect(EF_RESIST_FIRE):
            dump.td(gui.Label('Fire resistant', style=italic_style), align=-1, colspan=2)
            dump.tr()
        if self.has_status_effect(EF_RESIST_COLD):
            dump.td(gui.Label('Cold resistant', style=italic_style), align=-1, colspan=2)
            dump.tr()
        if self.has_status_effect(EF_RESIST_POISON):
            dump.td(gui.Label('Poison resistant', style=italic_style), align=-1, colspan=2)
            dump.tr()
        if self.has_status_effect(EF_PARALYSIS):
            dump.td(gui.Label('Paralysed', style=italic_style), align=-1, colspan=2)
            dump.tr()
        if self.has_status_effect(EF_POISON):
            dump.td(gui.Label('Poisoned', style=italic_style), align=-1, colspan=2)
            dump.tr()
        if self.has_status_effect(EF_SLEEP):
            dump.td(gui.Label('Asleep', style=italic_style), align=-1, colspan=2)
            dump.tr()
        if self.has_status_effect(EF_STUN):
            dump.td(gui.Label('Stunned', style=italic_style), align=-1, colspan=2)
            dump.tr()
        if self.has_status_effect(EF_GRAPPLE):
            dump.td(gui.Label('Grappled', style=italic_style), align=-1, colspan=2)
            dump.tr()
        if self.has_status_effect(EF_FEAR):
            dump.td(gui.Label('Afraid', style=italic_style), align=-1, colspan=2)
            dump.tr()
        if self.has_status_effect(EF_CURSE):
            dump.td(gui.Label('Cursed', style=italic_style), align=-1, colspan=2)
            dump.tr()            
        dump.td(gui.Spacer(height=20,width=280),colspan=2)
        dump.tr()
        dump.td(gui.Label("Stats", style=bold_style), align=-1,colspan=2)
        dump.tr()            
        if self.class_code==C_WARRIOR:
            dump.td(gui.Label('Strength', style=text_style), align=-1)
            dump.td(gui.Label(str(self.strength), style=text_style), align=-1)
            dump.tr()
            dump.td(gui.Label('Skill', style=text_style), align=-1)
            dump.td(gui.Label(str(self.skill), style=text_style), align=-1)
            dump.tr()
        elif self.class_code==C_PRIEST:
            dump.td(gui.Label('Valor', style=text_style), align=-1)
            dump.td(gui.Label(str(self.valor), style=text_style), align=-1)
            dump.tr()
            dump.td(gui.Label('Wisdom', style=text_style), align=-1)
            dump.td(gui.Label(str(self.wisdom), style=text_style), align=-1)
            dump.tr()
        elif self.class_code==C_ROGUE:
            dump.td(gui.Label('Daring', style=text_style), align=-1)
            dump.td(gui.Label(str(self.daring), style=text_style), align=-1)
            dump.tr()
            dump.td(gui.Label('Cunning', style=text_style), align=-1)
            dump.td(gui.Label(str(self.cunning), style=text_style), align=-1)
            dump.tr()
        elif self.class_code==C_MAGE:
            dump.td(gui.Label('Power', style=text_style), align=-1)
            dump.td(gui.Label(str(self.power), style=text_style), align=-1)
            dump.tr()
            dump.td(gui.Label('Subtlety', style=text_style), align=-1)
            dump.td(gui.Label(str(self.subtlety), style=text_style), align=-1)
            dump.tr()
        dump.td(gui.Label('Constitution', style=text_style), align=-1)
        dump.td(gui.Label(str(self.constitution), style=text_style), align=-1)
        dump.tr()
        dump.td(gui.Label('Stamina', style=text_style), align=-1)
        dump.td(gui.Label(str(self.stamina), style=text_style), align=-1)
        dump.tr()
        dump.td(gui.Label('Reflexes', style=text_style), align=-1)
        dump.td(gui.Label(str(self.reflexes), style=text_style), align=-1)
        dump.tr()
        dump.td(gui.Label('Learning', style=text_style), align=-1)
        dump.td(gui.Label(str(self.learning), style=text_style), align=-1)
        dump.tr()
        dump.td(gui.Spacer(height=20,width=280),colspan=2)
        dump.tr()
        dump.td(gui.Label("Defenses", style=bold_style), align=-1, colspan=2)
        dump.tr()  
        dump.td(gui.Label('Armor', style=text_style), align=-1)
        dump.td(gui.Label(str(self.current_armor()), style=text_style), align=-1)
        dump.tr()
        dump.td(gui.Label('Evasion', style=text_style), align=-1)
        dump.td(gui.Label(str(self.current_evasion()), style=text_style), align=-1)
        dump.tr()
        dump.td(gui.Label('Aggro', style=text_style), align=-1)
        dump.td(gui.Label(str(self.current_aggro()), style=text_style), align=-1)
        dump.tr()
        if self.melee_attack:
            dump.td(gui.Spacer(height=20,width=280),colspan=2)
            dump.tr()
            dump.td(gui.Label("Base melee attack", style=bold_style), align=-1, colspan=2)
            dump.tr()  
            if self.fire_melee:
                dump.td(gui.Label('Fire attack (less effective', style=italic_style), align=-1, colspan=2)
                dump.tr()  
                dump.td(gui.Label(' against fire-resistant monsters)', style=italic_style), align=-1, colspan=2)
                dump.tr()  
            if self.poison_melee:
                dump.td(gui.Label('Poison attack (less effective', style=italic_style), align=-1, colspan=2)
                dump.tr()  
                dump.td(gui.Label(' against poison-resistant monsters)', style=italic_style), align=-1, colspan=2)
                dump.tr()  
            if self.dragon_slaying_melee:
                dump.td(gui.Label('+100% base damage vs dragons', style=italic_style), align=-1, colspan=2)
                dump.tr()  
            if self.undead_slaying_melee:
                dump.td(gui.Label('+100% base damage vs undead', style=italic_style), align=-1, colspan=2)
                dump.tr()  
            dump.td(gui.Label('Accuracy ', style=text_style), align=-1)
            dump.td(gui.Label(str(self.current_accuracy()), style=text_style), align=-1)
            dump.tr()  
            dump.td(gui.Label('Rate of', style=text_style), align=-1)
            dump.td(gui.Label('1 per ' + str(round_to_1_dp(self.current_melee_period()/TIME_SCALAR)) + ' s', style=text_style), align=-1)
            dump.tr()  
            dump.td(gui.Label('Damage', style=text_style), align=-1)
            dump.td(gui.Label(str(self.melee_dice) + 'd' + str(self.melee_sides) + ' + ' + str(10*self.current_damage()) + '%', style=text_style), align=-1)
            dump.tr()  
            dump.td(gui.Label('DPS vs armor 0 evasion 0:   '+ str(round_to_2_dp(self.melee_dps_vs(0,0))), style=text_style), align=-1, colspan=2)
            dump.tr()  
            dump.td(gui.Label('DPS vs armor 3 evasion 3:   '+ str(round_to_2_dp(self.melee_dps_vs(3,3))), style=text_style), align=-1, colspan=2)
            dump.tr()  
            dump.td(gui.Label('DPS vs armor 6 evasion 0:   '+ str(round_to_2_dp(self.melee_dps_vs(6,0))), style=text_style), align=-1, colspan=2)
            dump.tr()  
            dump.td(gui.Label('DPS vs armor 0 evasion 6:   '+ str(round_to_2_dp(self.melee_dps_vs(0,6))), style=text_style), align=-1, colspan=2)
            dump.tr()  
        if self.missile_attack:
            dump.td(gui.Spacer(height=20,width=280),colspan=2)
            dump.tr()
            dump.td(gui.Label("Base missile attack", style=bold_style), align=-1,colspan=2)
            dump.tr()  
            if self.fire_missile:
                dump.td(gui.Label('Fire attack (less effective', style=italic_style), align=-1, colspan=2)
                dump.tr()  
                dump.td(gui.Label(' against fire-resistant monsters)', style=italic_style), align=-1, colspan=2)
                dump.tr()  
            if self.poison_missile:
                dump.td(gui.Label('Poison attack (less effective', style=italic_style), align=-1, colspan=2)
                dump.tr()  
                dump.td(gui.Label(' against poison-resistant monsters)', style=italic_style), align=-1, colspan=2)
                dump.tr()  
            dump.td(gui.Label('Aim', style=text_style), align=-1)
            dump.td(gui.Label(str(self.current_aim()), style=text_style), align=-1)
            dump.tr()  
            dump.td(gui.Label('Rate of', style=text_style), align=-1)
            dump.td(gui.Label('1 per ' + str(round_to_1_dp(self.current_missile_period()/TIME_SCALAR)) + ' s', style=text_style), align=-1)
            dump.tr()  
            dump.td(gui.Label('Damage', style=text_style), align=-1) 
            dump.td(gui.Label(str(self.missile_dice) + 'd' + str(self.missile_sides) + ' + ' + str(10*self.current_penetration()) + '%', style=text_style), align=-1)
            dump.tr()  
            dump.td(gui.Label('DPS vs armor 0 evasion 0:   '+ str(round_to_2_dp(self.missile_dps_vs(0,0))), style=text_style), align=-1, colspan=2)
            dump.tr()  
            dump.td(gui.Label('DPS vs armor 3 evasion 3:   '+ str(round_to_2_dp(self.missile_dps_vs(3,3))), style=text_style), align=-1, colspan=2)
            dump.tr()  
            dump.td(gui.Label('DPS vs armor 6 evasion 0:   '+ str(round_to_2_dp(self.missile_dps_vs(6,0))), style=text_style), align=-1, colspan=2)
            dump.tr()  
            dump.td(gui.Label('DPS vs armor 0 evasion 6:   '+ str(round_to_2_dp(self.missile_dps_vs(0,6))), style=text_style), align=-1, colspan=2)
            dump.tr()  
        dump.td(gui.Spacer(height=20,width=280),colspan=2)
        dump.tr()
        dump.td(gui.Label("Abilities", style=bold_style), align=-1,colspan=2)
        dump.tr()  
        for action in self.actions:
            lines = action.get_description()
            dump.td(gui.Spacer(height=5,width=280),colspan=2)
            dump.tr()
            dump.td(gui.Label(lines[0], style=italic_style), align=-1, colspan=2)
            dump.tr() 
            for line in lines[1:]:
                sublines = textwrap.wrap(line,30)
                dump.td(gui.Label(' '+sublines[0], style=text_style), align=-1, colspan=2)
                dump.tr()
                if len(sublines)>1:
                    for subline in sublines[1:]:
                        dump.td(gui.Label('  '+subline, style=text_style), align=-1, colspan=2)
                        dump.tr() 
        dump.td(gui.Spacer(height=20,width=280),colspan=2)
        dump.tr()
        return dump

    def melee_dps_vs(self,armor,evasion):
        target = 2 * (5 + self.current_accuracy() - evasion)
        if target<1:
            target = 1
        if target>19:
            target = 19
        mean_damage = 0
        for i in range(500):
            mean_damage = mean_damage + max(0,(self.melee_damage_roll(None) - armor))/500.0
        return mean_damage * (target / 20.0) * TIME_SCALAR / self.current_melee_period()

    def missile_dps_vs(self,armor,evasion):
        target = 2 * (5 + self.current_aim() - evasion)
        if target<1:
            target = 1
        if target>19:
            target = 19
        mean_damage = 0
        for i in range(500):
            mean_damage = mean_damage + max(0,(self.missile_damage_roll(None) - armor))/500.0
        return mean_damage * (target / 20.0) * TIME_SCALAR / self.current_missile_period()

    def melee_hit_roll(self,target):
        target = 2 * (5 + self.current_accuracy() - target.current_evasion())
        if target<1:
            target = 1
        if target>19:
            target = 19
        if randint(1,20)<=target:
            return True
        else:
            return False

    def missile_hit_roll(self,target):
        if self.magebane_missile:
            evasion = target.evasion
        else:
            evasion = target.current_evasion()
        target = 2 * (5 + self.current_aim() - evasion)
        if target<1:
            target = 1
        if target>19:
            target = 19
        if randint(1,20)<=target:
            return True
        else:
            return False

    def melee_damage_roll(self,m):
        x = 0
        for i in range(self.melee_dice):
            x = x + randint(1,self.melee_sides)
        base = int(floor(x * (10.5 + self.current_damage()) / 10))
        if m is not None:
            if self.poison_melee and m.poison_resistant:
                base = base / 3
            if self.fire_melee and m.fire_resistant:
                base = base / 3
            if self.dragon_slaying_melee and m.dragon:
                base = base + x
            if self.undead_slaying_melee and m.undead:
                base = base + x
        return base

    def missile_damage_roll(self,m):
        x = 0
        for i in range(self.missile_dice):
            x = x + randint(1,self.missile_sides)
        base = int(floor(x * (10.5 + self.current_penetration()) / 10))
        if m is not None:
            if self.poison_missile and m.poison_resistant:
                base = base / 3
            if self.fire_missile and m.fire_resistant:
                base = base / 3
        return base

    def take_damage(self,damage):
        if self.about_to_die or not self.alive:
            return
        for c in p.chars:
            if c.alive and c.current_stance==ST_INVULNERABLE:
                d.log('Damage prevented!')
                return
        if self.has_status_effect(EF_CURSE):
            damage = damage * 2
        if damage>=10 and damage<=20 and self.energy>=10:
            for this_action in self.actions:
                if this_action.action_code==CA_LUCKY_ESCAPE and (this_action.last_used is None or this_action.last_used<=w.elapsed_since_beginning-20):
                    if randint(1,100)<=this_action.success_chance():
                        this_action.last_used = w.elapsed_since_beginning
                        self.energy = self.energy - 12
                        d.sound_queue.append(S_GOOD_SPELL)
                        d.message_queue.append(('Lucky Escape!',self.image,COL_LIGHT))
                        d.log(self.name + ' uses Lucky Escape!')
                        return
        if damage > 0:
            self.flash_number = damage
            self.number_color = [255,0,0]
            self.number_starts = w.elapsed_since_beginning
            d.log(self.name + ' takes ' + str(damage) + ' damage')
        self.health = self.health - damage
        if self.health<=0:
            self.about_to_die = True
            self.die_at = w.elapsed_since_beginning + 1.5
            return
        if damage>1 and self.action_in_progress is not None and self.action_in_progress.disruptable():
            if self.concentration==False or damage>4:
                d.log('Action disrupted!')
                d.message_queue.append(('Agh! Action disrupted...',self.image,COL_LIGHT))
                self.action_in_progress = None
        if damage>2 and self.has_status_effect([EF_SLEEP]):
            self.status_effects[EF_SLEEP].level = max(0, self.status_effects[EF_SLEEP].level - (damage / 2))
            if self.status_effects[EF_SLEEP].level<=0:
                self.status_effects[EF_SLEEP].lasts_until = 0
        if damage>1 and self.energy_for_getting_hit:
            self.energy = min(self.max_energy,self.energy+2)
        if damage>=10:
            stun_seconds = (damage/5.0) - (self.constitution) - randint(0,3)
            if stun_seconds > 0:
                self.status_effects[EF_STUN] = Effect_parameters(stun_seconds)
                self.action_in_progress = None
                self.current_stance = None
                
    def die(self):
        front_row = self.front_row()
        self.pos = None
        self.alive = False
        self.about_to_die = False
        d.sound_queue.append(S_OUCH)
        d.log(self.name + ' incapacitated')
        d.message_queue.append(('Knocked unconscious!',self.image,COL_LIGHT))
        count = 0
        for c in p.chars:
            if c.pos is not None and c.alive:
                count = count + 1
        if count==0:
            d.log('All characters incapacitated')
            n_fled = 0
            n_dead = 0
            for c in p.chars:
                if c.fled:
                    n_fled = n_fled + 1
                else:
                    n_dead = n_dead + 1
            d.message_queue.append(('Alas, ' + str(n_dead) + ' characters have been incapacitated...',I_SKELETON,COL_LIGHT));
            if n_fled >= 0:
                d.message_queue.append(('But ' + str(n_fled) + ' survived to fight another day!',None,COL_LIGHT));
            d.quit_after_pause = True
        else:
            if front_row:
                if p.n_surviving_front_chars()==0:
                    p.push_char_forward()

    def flee(self):
        # return the number of characters that flee
        if self.can_act() and not self.has_status_effect(EF_GRAPPLE):
            gold = w.collected_gold / p.n_surviving_chars()
            front_row = self.front_row()
            self.slide_from = self.pos
            self.slide_to = POS_DOWN
            self.pos = None
            self.alive = False
            self.fled = True
            self.slide_start = w.elapsed_since_beginning
            self.slide_finish = w.elapsed_since_beginning + 0.3   
            d.log(self.name + ' flees')
            w.collected_gold = w.collected_gold - gold
            if self.xp > 0 or gold > 0:
                d.message_queue.append(('Fled with '+str(self.xp)+' XP and ' + str(gold) + ' gold',self.image,COL_LIGHT))
            else:
                d.message_queue.append(('Fled!',self.image,COL_LIGHT))                
            count = 0
            for c in p.chars:
                if c.pos is not None and c.alive:
                    count = count + 1
            if count==0:
                d.log('All characters have fled')
                d.message_queue.append(('All characters have fled the scene...',None,COL_LIGHT));
                d.quit_after_pause = True
            if front_row:
                if p.n_surviving_front_chars()==0:
                    p.push_char_forward()
            return 1
        else:
            return 0
    
    def can_act(self):
        return (self.alive and not self.about_to_die and self.action_in_progress is None and not self.has_status_effect(EF_PARALYSIS) and not self.has_status_effect(EF_SLEEP) and not self.has_status_effect(EF_STUN))

    def front_row(self):
        return (self.pos==POS_C_FRONT_1 or self.pos==POS_C_FRONT_2 or self.pos==POS_C_FRONT_3)

    def has_status_effect(self,ef_code):
        if ef_code in self.status_effects.keys():
            if self.status_effects[ef_code].lasts_until >= w.elapsed_since_beginning:
                return True
        return False
  
    def current_damage(self):
        bonus = 0
        if self.has_status_effect(EF_FRONTROWBUFF):
            bonus = bonus + 3
        if self.current_stance==ST_BERSERK:
            bonus = bonus + 3
        if self.has_status_effect(EF_FORCEBRAND):
            bonus = bonus + self.status_effects[EF_FORCEBRAND].level
        return self.damage + bonus

    def current_penetration(self):
        bonus = 0
        return self.penetration + bonus

    def current_evasion(self):
        bonus = 0
        if self.has_status_effect(EF_DODGE):
            bonus = bonus + 2
        if self.has_status_effect(EF_EV):
            bonus = bonus + self.status_effects[EF_EV].level
        if self.has_status_effect(EF_RIGHTEOUSNESS):
            bonus = bonus + 1
        if self.current_stance == ST_ALL_OUT_DEFENSE:
            bonus = bonus + 3
        if self.has_status_effect(EF_PARALYSIS) or self.has_status_effect(EF_SLEEP):
            bonus = bonus - 8
        if self.has_status_effect(EF_GRAPPLE) or self.has_status_effect(EF_STUN):
            bonus = bonus - 3
        return max(0,self.evasion + bonus)

    def current_armor(self):
        bonus = 0
        if self.current_stance == ST_ALL_OUT_DEFENSE:
            bonus = bonus + 3
        if self.has_status_effect(EF_SHIELD):
            bonus = bonus + self.status_effects[EF_SHIELD].level
        return self.armor + bonus

    def current_aggro(self):
        bonus = 0
        if self.has_status_effect(EF_RIGHTEOUSNESS):
            bonus = bonus + 1
        if self.has_status_effect(EF_TAUNT):
            bonus = bonus + 1
        if self.has_status_effect(EF_INSULT):
            bonus = bonus + 2
        if self.has_status_effect(EF_CURSE):
            bonus = bonus + 6
        return self.aggro + bonus

    def current_missile_period(self):
        bonus = 0
        rapidity = self.rapidity + bonus
        return 5 * pow(0.9,rapidity)

    def current_melee_period(self):
        bonus = 0
        for a in self.actions:
            if a.action_code==CA_LEADER:
                if self.pos==POS_C_FRONT_2:
                    bonus = bonus + 1
        if self.current_stance==ST_BERSERK:
            bonus = bonus + 3            
        speed = self.speed + bonus
        return 5 * pow(0.9,speed)

    def current_aim(self):
        bonus = 0
        return self.aim + bonus

    def current_accuracy(self):
        bonus = 0
        return self.accuracy + bonus
    
class Party:
    def __init__(self):
        self.initialise()

    def initialise(self):
        self.chars = []

    def reset(self,charsheets):
        self.initialise()
        posses = [POS_C_FRONT_2,POS_C_FRONT_1,POS_C_FRONT_3,POS_C_BACK_2,POS_C_BACK_1,POS_C_BACK_3]
        for i in range(len(charsheets)):
            self.chars.append(Character(charsheets[i],i,posses[i]))
        if len(self.chars)==6:
            self.xp_multiplier = 100
        elif len(self.chars)==5:
            self.xp_multiplier = 130
        elif len(self.chars)==4:
            self.xp_multiplier = 160
        else:
            self.xp_multiplier = 200
            
    def get_char_at_pos(self,pos):
        for c in self.chars:
            if c.pos==pos:
                return c
        return None

    def n_surviving_front_chars(self):
        count = 0
        for c in self.chars:
            if c.pos is not None and c.front_row() and c.alive and not c.about_to_die:
                count = count + 1
        return count

    def n_surviving_back_chars(self):
        count = 0
        for c in self.chars:
            if c.pos is not None and (not c.front_row()) and c.alive and not c.about_to_die:
                count = count + 1
        return count

    def push_char_forward(self):
        push_to = self.last_free_front_pos()
        if push_to is None:
            return False
        for pos in [POS_C_BACK_2,POS_C_BACK_1,POS_C_BACK_3]:
            c = self.get_char_at_pos(pos)
            if c is not None:
                c.slide_from = c.pos
                c.pos = push_to
                c.slide_to = push_to
                c.slide_start = w.elapsed_since_beginning
                c.slide_finish = w.elapsed_since_beginning + 0.1
                return True
        return False

    def last_free_front_pos(self):
        if self.get_char_at_pos(POS_C_FRONT_2) is None:
            return POS_C_FRONT_2
        if self.get_char_at_pos(POS_C_FRONT_1) is None:
            return POS_C_FRONT_1
        if self.get_char_at_pos(POS_C_FRONT_3) is None:
            return POS_C_FRONT_3
        return None

    def last_free_back_pos(self):
        if self.get_char_at_pos(POS_C_BACK_2) is None:
            return POS_C_BACK_2
        if self.get_char_at_pos(POS_C_BACK_1) is None:
            return POS_C_BACK_1
        if self.get_char_at_pos(POS_C_BACK_3) is None:
            return POS_C_BACK_3
        return None

    def move_character(self,c):
        if self.n_surviving_chars()==1:
            return False
        if c.alive and not c.about_to_die and c.pos is not None:
            if c.front_row():
                if p.n_surviving_front_chars()==1:
                    return False
                push_to = self.last_free_back_pos()
                if push_to is None:
                    return False
            else:
                push_to = self.last_free_front_pos()
                if push_to is None:
                    return False
            c.slide_from = c.pos
            c.pos = push_to
            c.slide_to = push_to
            c.slide_start = w.elapsed_since_beginning
            c.slide_finish = w.elapsed_since_beginning + 0.1
            return True
        else:
            return False

    def move_character_to(self,c,pos):
        if c.front_row():
            if (self.get_char_at_pos(pos) is None) and self.n_surviving_front_chars()==1 and (pos==POS_C_BACK_1 or pos==POS_C_BACK_2 or pos==POS_C_BACK_3):
                return False
        if c.alive and not c.about_to_die and c.pos is not None and c.pos<>pos and not c.has_status_effect(EF_GRAPPLE):
            displaced = self.get_char_at_pos(pos)
            if displaced is not None:
                if (not displaced.can_act()) or displaced.has_status_effect(EF_GRAPPLE):
                    return False
                else:
                    displaced.slide_from = displaced.pos
                    displaced.pos = c.pos
                    displaced.slide_to = c.pos
                    displaced.slide_start = w.elapsed_since_beginning
                    displaced.slide_finish = w.elapsed_since_beginning + 0.1
                    c.slide_from = c.pos
                    c.pos = pos
                    c.slide_to = pos
                    c.slide_start = w.elapsed_since_beginning
                    c.slide_finish = w.elapsed_since_beginning + 0.1
            else:
                c.slide_from = c.pos
                c.pos = pos
                c.slide_to = pos
                c.slide_start = w.elapsed_since_beginning
                c.slide_finish = w.elapsed_since_beginning + 0.1
            return True
        else:
            return False

    def random_target_from(self,posses,hated_by):
        options = []
        preferences = []
        for pos in posses:
            t = self.get_char_at_pos(pos)
            if t is not None:
                options.append(t)
                if hated_by is not None and opposite_sides(hated_by.pos,t.pos):
                    this_preference = 0.1
                else:
                    this_preference = 1.0
                this_preference = this_preference * pow(2.0,t.current_aggro())
                preferences.append(this_preference)
        if len(options)==0:
            return None
        cumulative_preferences = []
        so_far = 0
        for preference in preferences:
            cumulative_preferences.append(so_far + float(preference) / sum(preferences))
            so_far = so_far + float(preference) / sum(preferences)
        u = uniform(0,1)
        for i in range(len(preferences)):
            if cumulative_preferences[i] >= u:
                return options[i]

    def random_target(self,hated_by):
        return self.random_target_from([POS_C_FRONT_1,POS_C_FRONT_2,POS_C_FRONT_3,POS_C_BACK_1,POS_C_BACK_2,POS_C_BACK_3],hated_by)

    def random_front_target(self,hated_by):
        return self.random_target_from([POS_C_FRONT_1,POS_C_FRONT_2,POS_C_FRONT_3],hated_by)

    def random_back_target(self,hated_by):
        return self.random_target_from([POS_C_BACK_1,POS_C_BACK_2,POS_C_BACK_3],hated_by)
   
    def n_surviving_chars(self):
        count = 0
        for c in self.chars:
            if c.pos is not None and c.alive and not c.about_to_die:
                count = count + 1
        return count

class Monster_action:
    def __init__(self):
        self.last_used = None
        self.n_times_used = 0
    
    def name(self):
        return ''

    def warning_message(self):
        return ''

    def target_friend(self):
        return False
    
    def target_enemy(self):
        return False

    def enemy_target_back_row_ok(self):
        return True # but usually suppressed by target_enemy=False
    
    def enemy_target_front_row_ok(self):
        return True # but usually suppressed by target_enemy=False

    def period(self):
        return None

    def likelihood_increase_until_n_uses(self):
        return 1

    def n_uses(self):
        return 1

    def delay(self):
        return 0.0

    def cooldown(self):
        return None

    def gc(self):
        return None;

    def back_row_ok(self):
        return True

    def front_row_ok(self):
        return True

    def ok(self):
        if self.m.front_row() and not self.front_row_ok():
            return False
        if not self.m.front_row() and not self.back_row_ok():
            return False
        if (self.target_enemy() or self.target_friend()) and len(self.valid_targets())==0:
            return False
        if self.cooldown() is not None and self.last_used is not None and self.last_used+self.cooldown()>w.elapsed_since_beginning:
            return False
        return True

    def friend_target_ok(self,m):
        if m==self.m:
            return False
        if not m.alive:
            return False
        if m.about_to_die:
            return False
        return True

    def enemy_target_ok(self,c):
        if c.front_row() and not self.enemy_target_front_row_ok():
            return False
        if not c.front_row() and not self.enemy_target_back_row_ok():
            return False
        if not c.alive:
            return False
        if c.about_to_die:
            return False
        return True

    def valid_targets(self):
        if self.target_friend():
            targets = []
            for m in e.mobs:
                if self.friend_target_ok(m):
                    targets.append(m)
            return targets
        elif self.target_enemy():
            targets = []
            for c in p.chars:
                if self.enemy_target_ok(c):
                    targets.append(c)
            return targets
        else:
            return None

    def trigger(self,target):
        if self.delay()>0:
            self.m.action_in_progress = self
            self.m.action_fires = w.elapsed_since_beginning + self.delay()
            self.m.target_of_action_in_progress = target
            if self.warning_message()<>'':
                d.message_queue.append((self.warning_message(),self.m.image,COL_RED))
        else:
            self.resolve(target)
        self.last_used = w.elapsed_since_beginning
        self.n_times_used = self.n_times_used + 1
        if self.gc() is not None:
            self.m.gc_until = w.elapsed_since_beginning + self.gc()

    def resolve(self,target):
        if self.prevented():
            d.log(self.m.name + ' was prevented from using ' + self.name()) 
            d.message_queue.append(('Blocked',self.m.image,COL_LIGHT))
        elif (self.target_friend() and not self.friend_target_ok(target)) or (self.target_enemy() and not self.enemy_target_ok(target)):
            d.message_queue.append(('Action failed',self.m.image,COL_LIGHT))
        else:
            self.m.flash_image = I_VERYPALE
            if self.name()<>'':
                if target is not None:
                    d.log(self.m.name + ' uses ' + self.name() + ' on ' + target.name)
                else:
                    d.log(self.m.name + ' uses ' + self.name())
                if target is not None:
                    d.message_queue.append((self.name() + ' [' + target.name + ']',self.m.image,COL_LIGHT))
                else:
                    d.message_queue.append((self.name(),self.m.image,COL_LIGHT))
            self.make_stuff_happen(target)

    def make_stuff_happen(self,target):
        pass # stub

    def prevented(self):
        return False

    def get_description(self):
        return ''

    def disruptable(self):
        return False
    
    def blockable(self):
        return False

    def semi_blockable(self):
        return False

    def deflectable(self):
        return False

    def fiery(self):
        return False

    def freezy(self):
        return False

    def paralysing(self):
        return False

    def scary(self):
        return False

    def sleepy(self):
        return False

    def bolt(self):
        return False

    def finishing(self):
        return False

    def silencing(self):
        return False

    def epic_blockable(self):
        return False

    def leap_awayable(self):
        return False

    def take_coverable(self):
        return False

class Snipe_MA(Monster_action):
# troglodyte archer version
    def __init__(self):
        return Monster_action.__init__(self)
    
    def name(self):
        return 'Snipe'

    def warning_message(self):
        return 'Nocks an arrow'

    def delay(self):
        return 3.0

    def period(self):
        return 10.0

    def gc(self):
        return self.delay()+1

    def min_damage(self):
        return 5

    def max_damage(self):
        return 15

    def front_row_ok(self):
        return False

    def target_enemy(self):
        return True

    def enemy_target_front_row_ok(self):
        return False

    def deflectable(self):
        return True

    def prevented(self):
        for c in p.chars:
            if c.alive and not c.about_to_die and c.current_stance is not None and c.current_stance==ST_DEFLECT:
                c.current_stance = None
                return True
            if c.alive and not c.about_to_die and c.current_stance is not None and c.current_stance==ST_COUNTER_MANY:
                c.current_stance = None
                if randint(1,100) <= min(70,success_chance(12-self.m.level)):
                    return True
                else:
                    d.message_queue.append(('Distraction failed',c.image,COL_LIGHT))
        return False

    def make_stuff_happen(self,target):
        d.sound_queue.append(self.m.missile_hit_sound)
        damage = randint(self.min_damage(),self.max_damage())
        target.take_damage(max(0,damage-target.current_armor()))

    def get_description(self):
        return 'When it gets ready to snipe you, tell one of your front-row characters to use the Deflect ability or one of your back-row characters will get hit.'

class Snipe_Dark_Elf_MA(Snipe_MA):
    def __init__(self):
        return Monster_action.__init__(self)

    def delay(self):
        return 2.0

    def period(self):
        return 7.0

    def min_damage(self):
        return 6

    def max_damage(self):
        return 18

class Snipe_Naga_MA(Snipe_MA):
    def __init__(self):
        return Monster_action.__init__(self)

    def delay(self):
        return 2.0

    def period(self):
        return 10.0

    def min_damage(self):
        return 6

    def max_damage(self):
        return 18

class Snipe_Poltergeist_MA(Snipe_MA):
    def __init__(self):
        return Monster_action.__init__(self)

    def warning_message(self):
        return 'Picks up debris to hurl'
    
    def delay(self):
        return 2.0

    def period(self):
        return 3.0

    def front_row_ok(self):
        return True

    def min_damage(self):
        return 1

    def max_damage(self):
        return 8

    def make_stuff_happen(self,target):
        d.sound_queue.append(S_MONHIT)
        damage = randint(self.min_damage(),self.max_damage())
        target.take_damage(max(0,damage-target.current_armor()))
        
class Snipe_Orc_Boss_MA(Snipe_MA):
    def __init__(self):
        return Monster_action.__init__(self)

    def warning_message(self):
        return 'Loads his crossbow'

    def delay(self):
        return 3.0

    def period(self):
        return 12.0

    def min_damage(self):
        return 10

    def max_damage(self):
        return 30

class Snipe_Manticore_MA(Snipe_MA):
    def __init__(self):
        return Monster_action.__init__(self)

    def warning_message(self):
        return 'Raises its tail'

    def delay(self):
        return 1.5

    def period(self):
        return 4

    def likelihood_increase_until_n_uses(self):
        return 2

    def n_uses(self):
        return 1

    def min_damage(self):
        return 8

    def max_damage(self):
        return 20

    def make_stuff_happen(self,target):
        d.sound_queue.append(S_SHOOT)
        damage = randint(self.min_damage(),self.max_damage())
        target.take_damage(max(0,damage-target.current_armor()))
    
class Summon_Snakes_MA(Monster_action):
    def __init__(self):
        return Monster_action.__init__(self)
    
    def name(self):
        return 'Summon Snakes'

    def ok(self):
        if (6-e.n_mobs_taking_up_space())>=self.n_summoned():
            return Monster_action.ok(self)
        else:
            return False
        
    def period(self):
        return 5.0

    def cooldown(self):
        return 3.0

    def n_summoned(self):
        return 3

    def choose_from(self):
        return [MOB_SNAKE]

    def make_stuff_happen(self,target):
        if self.m.front_row() and e.last_free_back_pos() is not None:
            self.m.slide_from = self.m.pos
            self.m.pos = e.last_free_back_pos()
            self.m.slide_to = self.m.pos
            self.m.slide_start = w.elapsed_since_beginning
            self.m.slide_finish = w.elapsed_since_beginning + 0.1
        summoned = 0
        to_summon = self.n_summoned()
        for i in range(to_summon):
            new_critter = e.add_mob(choice(self.choose_from()))
            if new_critter is not None:
                new_critter.slide_from = None
                new_critter.fade_in = True
                new_critter.fade_start = w.elapsed_since_beginning
                new_critter.fade_finish = w.elapsed_since_beginning + 0.5
                new_critter.summoner = self.m
                summoned = summoned + 1
        if summoned>0:
            d.sound_queue.append(S_BAD_SPELL)
        if (not self.m.front_row()) and e.n_surviving_front_mobs()==0:
            self.m.slide_from = self.m.pos
            self.m.pos = e.last_free_front_pos()
            self.m.slide_to = self.m.pos
            self.m.slide_start = w.elapsed_since_beginning
            self.m.slide_finish = w.elapsed_since_beginning + 0.1

    def thing_summoned(self):
        return 'poisonous little critters'

    def get_description(self):
        return 'Summons up to ' + str(self.n_summoned()) + ' ' + self.thing_summoned() + '. Cannot be prevented.'

class Summon_Animals_MA(Summon_Snakes_MA):
    def __init__(self):
        return Monster_action.__init__(self)
    
    def name(self):
        return 'Summon Animals'

    def period(self):
        return 6.5

    def n_summoned(self):
        return 3

    def choose_from(self):
        return [MOB_CENTIPEDE,MOB_SNAKE,MOB_COBRA,MOB_GIANT_FIREFLY,MOB_GIANT_WASP,MOB_MILLIPEDE,MOB_PYTHON,MOB_WOLF,MOB_WOLF,MOB_BEAR,MOB_BEAR,MOB_SCORPION,MOB_BEETLE,MOB_GIANT_ANT,MOB_BIG_SPIDER]

    def thing_summoned(self):
        return 'animals'

class Summon_Cobras_MA(Summon_Snakes_MA):
    def __init__(self):
        return Monster_action.__init__(self)
    
    def name(self):
        return 'Summon Cobras'

    def cooldown(self):
        return 5.0

    def n_summoned(self):
        return 3

    def choose_from(self):
        return [MOB_COBRA]

    def thing_summoned(self):
        return 'spitting cobras'

class Summon_Evil_Trash_MA(Summon_Snakes_MA):
    def __init__(self):
        return Monster_action.__init__(self)
    
    def name(self):
        return 'Summon Minions'

    def n_summoned(self):
        return 3

    def choose_from(self):
        return [MOB_KOBOLD,MOB_ORC_WRETCH,MOB_HARPY,MOB_ORC_ROCK_THROWER,MOB_TROGLODYTE,MOB_ORC_WARRIOR,MOB_SKELETON,MOB_TROGLODYTE_ARCHER,MOB_ORC_SLAVEMASTER,MOB_ORC_WITH_MANCATCHER,MOB_SPECTRE,MOB_TROGLODYTE_SHAMAN,MOB_THUG,MOB_BIG_KOBOLD,MOB_GOBLIN,MOB_YOUNG_NECROMANCER]

    def thing_summoned(self):
        return 'weak evil monsters'

class Summon_Golem_MA(Summon_Snakes_MA):
    def __init__(self):
        return Monster_action.__init__(self)
    
    def name(self):
        return 'Summon Golem'

    def n_summoned(self):
        return 1

    def ok(self):
        if Summon_Snakes_MA.ok(self):
            count = 0
            for m in e.mobs:
                if m.alive and not m.about_to_die and m.construct:
                    count = count + 1
            if count==0:
                return True
        return False

    def period(self):
        return 30.0

    def likelihood_increase_until_n_uses(self):
        return 10.0

    def n_uses(self):
        return 1
    
    def choose_from(self):
        return [MOB_STONE_GOLEM]

    def thing_summoned(self):
        return 'golem'

class Summon_Orcs_MA(Summon_Snakes_MA):
    def __init__(self):
        return Monster_action.__init__(self)
    
    def name(self):
        return 'Summon Orcs'

    def n_summoned(self):
        return 5

    def choose_from(self):
        return [MOB_ORC_WRETCH,MOB_ORC_ROCK_THROWER,MOB_ORC_WARRIOR,MOB_ORC_ROCK_THROWER,MOB_ORC_WARRIOR,MOB_ORC_AGITATOR,MOB_ORC_SLAVEMASTER,MOB_ORC_CHAMPION]

    def thing_summoned(self):
        return 'assorted orcs'
    
class Summon_Reptiles_MA(Summon_Snakes_MA):
    def __init__(self):
        return Monster_action.__init__(self)
    
    def name(self):
        return 'Summon Reptiles'

    def n_summoned(self):
        return 3

    def choose_from(self):
        return [MOB_SNAKE,MOB_COBRA,MOB_PYTHON,MOB_SNAKE,MOB_COBRA,MOB_PYTHON,MOB_TURTLOID,MOB_NAGA_ARCHER,MOB_NAGA_WARRIOR,MOB_BASILISK]

    def thing_summoned(self):
        return 'assorted reptiles'

class Summon_Troglodytes_MA(Summon_Snakes_MA):
    def __init__(self):
        return Monster_action.__init__(self)
    
    def name(self):
        return 'Summon Troglodytes'

    def n_summoned(self):
        return 3

    def choose_from(self):
        return [MOB_TROGLODYTE,MOB_TROGLODYTE_ARCHER,MOB_TROGLODYTE_ARCHER,MOB_TROGLODYTE_SHAMAN,MOB_TROGLODYTE_PRIEST]

    def thing_summoned(self):
        return 'assorted troglodytes'

class Summon_Various_MA(Summon_Snakes_MA):
    def __init__(self):
        return Monster_action.__init__(self)

    def period(self):
        return 3.0

    def cooldown(self):
        return 2.0

    def name(self):
        return 'Summon Monsters'

    def n_summoned(self):
        return 3

    def choose_from(self):
        return [MOB_YOUNG_NECROMANCER,MOB_BIG_SPIDER,MOB_HELLHOUND,MOB_DARK_ELF_DRUID,MOB_MUMMY,MOB_POLTERGEIST,MOB_BERSERKER,MOB_BLAST_SPORE,MOB_COBRA,MOB_GIANT_FIREFLY,MOB_GIANT_WASP,MOB_JELLY,MOB_MILLIPEDE,MOB_ORC_WARRIOR,MOB_PYTHON,MOB_SKELETON,MOB_TROGLODYTE_ARCHER,MOB_WOLF,MOB_LEPRECHAUN,MOB_OOZE,MOB_ORC_SLAVEMASTER,MOB_ORC_WITH_MANCATCHER,MOB_SPECTRE,MOB_TROGLODYTE_SHAMAN,MOB_CLAY_GOLEM,MOB_NAGA_ARCHER,MOB_NAGA_WARRIOR,MOB_OGRE,MOB_ORC_CHAMPION,MOB_TROGLODYTE_PRIEST,MOB_TURTLOID,MOB_WHITE_WOLF,MOB_WIGHT,MOB_WRAITH,MOB_ARTIFICER,MOB_BASILISK,MOB_BLACK_KNIGHT,MOB_BLACK_OGRE,MOB_BURNING_GHOST,MOB_DARK_DWARF,MOB_DARK_ELF_ARCHER,MOB_DARK_PRIEST,MOB_LIME_JELLY,MOB_MAGE,MOB_MAGEBANE_EYE,MOB_MUTE_GHOST,MOB_SHADOW,MOB_MANTICORE,MOB_TROLL,MOB_FROZEN_SPIRIT,MOB_THUG,MOB_BIG_KOBOLD,MOB_GOBLIN,MOB_BEETLE,MOB_MINOTAUR,MOB_SCORPION,MOB_BEAR]

    def thing_summoned(self):
        return 'assorted monsters'

class Summon_Front_MA(Summon_Snakes_MA):
    def __init__(self):
        return Monster_action.__init__(self)

    def ok(self):
        return self.m.front_row() and Summon_Snakes_MA.ok(self)

    def period(self):
        return 1.0

    def cooldown(self):
        return 10.0

    def name(self):
        return 'Summon Protector'

    def n_summoned(self):
        return 1

    def choose_from(self):
        return [MOB_OOZE,MOB_CLAY_GOLEM,MOB_NAGA_WARRIOR,MOB_OGRE,MOB_ORC_CHAMPION,MOB_WHITE_WOLF,MOB_WIGHT,MOB_BASILISK,MOB_BLACK_KNIGHT,MOB_BLACK_OGRE,MOB_DARK_DWARF,MOB_LIME_JELLY,MOB_TROLL,MOB_MINOTAUR,MOB_HELLHOUND]
   
    def thing_summoned(self):
        return 'a strong front row monster'

class Summon_Bodyguard_MA(Summon_Snakes_MA):
    def __init__(self):
        return Monster_action.__init__(self)

    def period(self):
        return 30

    def likelihood_increase_until_n_uses(self):
        return 30

    def n_uses(self):
        return 1
    
    def name(self):
        return 'Summon Bodyguard'

    def n_summoned(self):
        return 1

    def choose_from(self):
        return [MOB_COBRA,MOB_JELLY,MOB_MILLIPEDE,MOB_ORC_WARRIOR,MOB_PYTHON,MOB_SKELETON,MOB_WOLF,MOB_OOZE,MOB_ORC_WITH_MANCATCHER,MOB_CLAY_GOLEM,MOB_NAGA_WARRIOR,MOB_OGRE,MOB_ORC_CHAMPION,MOB_TURTLOID,MOB_WHITE_WOLF,MOB_WIGHT,MOB_BLACK_KNIGHT,MOB_BLACK_OGRE,MOB_DARK_DWARF,MOB_TROLL,MOB_THUG,MOB_BEETLE,MOB_MINOTAUR,MOB_SCORPION,MOB_BEAR,MOB_BIG_SPIDER,MOB_HELLHOUND]

    def thing_summoned(self):
        return 'a front row monster'
    
class Summon_Undead_MA(Summon_Snakes_MA):
    def __init__(self):
        return Monster_action.__init__(self)
    
    def name(self):
        return 'Summon Undead'

    def period(self):
        return 4

    def n_summoned(self):
        return 1

    def choose_from(self):
        return [MOB_SKELETON,MOB_SKELETON,MOB_SKELETON,MOB_SPECTRE,MOB_SPECTRE,MOB_WIGHT,MOB_WIGHT,MOB_WRAITH,MOB_WRAITH,MOB_BURNING_GHOST,MOB_MUTE_GHOST,MOB_SHADOW,MOB_SHADOW,MOB_FROZEN_SPIRIT,MOB_FROZEN_SPIRIT,MOB_MUMMY,MOB_POLTERGEIST]

    def thing_summoned(self):
        return 'random undead'

class Summon_Low_Undead_MA(Summon_Snakes_MA):
    def __init__(self):
        return Monster_action.__init__(self)
    
    def name(self):
        return 'Summon Undead'

    def n_summoned(self):
        return 1

    def choose_from(self):
        return [MOB_SKELETON,MOB_SKELETON,MOB_SKELETON,MOB_SPECTRE,MOB_FROZEN_SPIRIT,MOB_POLTERGEIST]

    def thing_summoned(self):
        return 'random low-grade undead'
    
class Summon_Wretch_MA(Summon_Snakes_MA):
    def __init__(self):
        return Monster_action.__init__(self)
    
    def name(self):
        return 'Summon Wretch'

    def period(self):
        return 4

    def n_summoned(self):
        return 1
   
    def choose_from(self):
        return [MOB_ORC_WRETCH]

    def thing_summoned(self):
        return 'pitiful orcish thrall'
            
class Gaze_Paralysis_MA(Monster_action):
    def __init__(self):
        return Monster_action.__init__(self)
    
    def name(self):
        return 'Paralysing Gaze'

    def warning_message(self):
        return 'Gazes at you'

    def period(self):
        return 15.0

    def delay(self):
        return 3

    def cooldown(self):
        return 10.0

    def gc(self):
        return 3.0

    def paralysing(self):
        return True
    
    def prevented(self):
        for c in p.chars:
            if c.alive and not c.about_to_die and c.current_stance is not None and c.current_stance==ST_COUNTER_PARALYSIS:
                c.current_stance = None
                return True
            if c.alive and not c.about_to_die and c.current_stance is not None and c.current_stance==ST_COUNTER_MANY:
                c.current_stance = None
                if randint(1,100) <= min(70,success_chance(12-self.m.level)):
                    return True
                else:
                    d.message_queue.append(('Distraction failed',c.image,COL_LIGHT))
        return False

    def duration(self):
        return randint(5,10)

    def make_stuff_happen(self,target):
        d.sound_queue.append(S_BAD_SPELL)
        for c in p.chars:
            if c.alive and not c.about_to_die:
                immune = False
                for a in c.actions:
                    if a.action_code==CA_STEELY_GAZE or a.action_code==CA_FREE_ACTION:
                        immune = True
                        continue
                if c.has_status_effect(EF_MEDITATION):
                    immune = True
                if not immune:
                    c.status_effects[EF_PARALYSIS] = Effect_parameters(self.duration())
                    c.action_in_progress = None
                    c.current_stance = None

    def get_description(self):
        return 'Paralyses your entire party. Can be countered with the "Counter: Paralysis" spell.'

class Sting_Paralysis_MA(Monster_action):
    def __init__(self):
        return Monster_action.__init__(self)
    
    def period(self):
        return 10.0

    def cooldown(self):
        return 10.0

    def paralysing(self):
        return True
 
    def duration(self):
        return randint(5,10)

    def back_row_ok(self):
        return False

    def target_enemy(self):
        return True

    def enemy_target_back_row_ok(self):
        return False
    
    def make_stuff_happen(self,target):
        if self.m.melee_hit_roll(target):
            d.sound_queue.append(self.m.melee_hit_sound)
            damage = self.m.melee_damage_roll()
            armor = target.current_armor()
            if damage > armor:
                target.take_damage(max(0,damage-armor))
                self.m.flash_image = I_PALE
                if target.alive and not target.about_to_die:
                    immune = False
                    for a in target.actions:
                        if a.action_code==CA_STEELY_GAZE or a.action_code==CA_FREE_ACTION:
                            immune = True
                            continue
                    if target.has_status_effect(EF_MEDITATION):
                        immune = True
                    if immune:
                        d.message_queue.append(('Failed to paralyse ' + target.name,self.m.image,COL_LIGHT))
                    else:               
                        target.status_effects[EF_PARALYSIS] = Effect_parameters(self.duration())
                        target.action_in_progress = None
                        target.current_stance = None
                        d.message_queue.append(('Paralysing Sting!  [' + target.name + ']',self.m.image,COL_LIGHT))
            else:
                d.message_queue.append(('Failed to paralyse ' + target.name,self.m.image,COL_LIGHT))
        else:
            d.sound_queue.append(self.m.melee_miss_sound)
            d.message_queue.append(('Failed to paralyse ' + target.name,self.m.image,COL_LIGHT))

    def get_description(self):
        return 'Its sting paralyses a character for a few seconds - if it can penetrate their armour.'

class Gaze_Scare_MA(Monster_action):
    def __init__(self):
        return Monster_action.__init__(self)
    
    def name(self):
        return 'Terrifying Gaze'

    def warning_message(self):
        return 'Gazes at you'

    def likelihood_increase_until_n_uses(self):
        return 2

    def n_uses(self):
        return 1

    def period(self):
        return 4.0

    def delay(self):
        return 2.5

    def cooldown(self):
        return 5.0

    def gc(self):
        return 3.0

    def target_enemy(self):
        return True
    
    def duration(self):
        return randint(8,12)

    def scary(self):
        return True

    def prevented(self):
        for c in p.chars:
            if c.alive and not c.about_to_die and c.current_stance is not None and c.current_stance==ST_COUNTER_FEAR:
                c.current_stance = None
                return True
            if c.alive and not c.about_to_die and c.current_stance is not None and c.current_stance==ST_COUNTER_MANY:
                c.current_stance = None
                if randint(1,100) <= min(70,success_chance(12-self.m.level)):
                    return True
                else:
                    d.message_queue.append(('Distraction failed',c.image,COL_LIGHT))
        return False

    def make_stuff_happen(self,target):
        d.sound_queue.append(S_BAD_SPELL)
        if target.alive and not target.about_to_die:
            immune = False
            for a in target.actions:
                if a.action_code==CA_STEELY_GAZE or a.action_code==CA_RESIST_FEAR:
                    immune = True
                    continue
            if target.has_status_effect(EF_MEDITATION):
                immune = True
            if not immune:
                target.status_effects[EF_FEAR] = Effect_parameters(self.duration())
                target.action_in_progress = None
                target.current_stance = None

    def get_description(self):
        return 'Makes one of your characters afraid. Can be countered with the "Counter: Fear" spell.'

class Curse_MA(Monster_action):
    def __init__(self):
        return Monster_action.__init__(self)
    
    def name(self):
        return 'Curse'

    def likelihood_increase_until_n_uses(self):
        return 3

    def n_uses(self):
        return 1

    def period(self):
        return 6.0

    def cooldown(self):
        return 5.0

    def duration(self):
        return randint(20,40)

    def make_stuff_happen(self,target):
        d.sound_queue.append(S_BAD_SPELL)
        options = []
        for c in p.chars:
            if c.alive and not c.about_to_die:
                options.append(c)
        target = choice(options)
        target.status_effects[EF_CURSE] = Effect_parameters(self.duration())

    def get_description(self):
        return 'Curses one of your characters. Cannot be prevented. All monsters will try to attack this character and they will take extra damage from all sources.'

class Howl_MA(Monster_action):
    def __init__(self):
        return Monster_action.__init__(self)
    
    def name(self):
        return 'Hoooowlll!'

    def likelihood_increase_until_n_uses(self):
        return 2

    def n_uses(self):
        return 1

    def period(self):
        return 15.0

    def cooldown(self):
        return 5.0

    def gc(self):
        return 3.0
   
    def duration(self):
        return randint(4,8)

    def scary(self):
        return True

    def prevented(self):
        for c in p.chars:
            if c.alive and not c.about_to_die and c.current_stance is not None and c.current_stance==ST_COUNTER_FEAR:
                c.current_stance = None
                return True
            if c.alive and not c.about_to_die and c.current_stance is not None and c.current_stance==ST_COUNTER_MANY:
                c.current_stance = None
                if randint(1,100) <= min(70,success_chance(12-self.m.level)):
                    return True
                else:
                    d.message_queue.append(('Distraction failed',c.image,COL_LIGHT))
        return False

    def make_stuff_happen(self,target):
        d.sound_queue.append(S_BAD_SPELL)
        while 1:
            target = p.random_target(None)
            if target.alive and not target.about_to_die:
                immune = False
                for a in target.actions:
                    if a.action_code==CA_RESIST_FEAR:
                        immune = True
                        continue
                if target.has_status_effect(EF_MEDITATION):
                    immune = True
                if not immune:
                    target.status_effects[EF_FEAR] = Effect_parameters(self.duration())
                    target.action_in_progress = None
                    target.current_stance = None
            if not one_in(3):
                return

    def get_description(self):
        return 'Makes one or more of your characters afraid. Can be countered with the "Counter: Fear" spell.'

class Shriek_Scare_MA(Howl_MA):
    def __init__(self):
        return Monster_action.__init__(self)
    
    def name(self):
        return 'EEEEeeeeEEEEEE!!!'

    def period(self):
        return 8.0

    def cooldown(self):
        return 3.0

    def gc(self):
        return 1.0
       
class Gaze_Sleep_MA(Monster_action):
    def __init__(self):
        return Monster_action.__init__(self)
    
    def name(self):
        return 'Hypnotic Gaze'

    def warning_message(self):
        return 'Gazes at you'

    def sleepy(self):
        return True

    def period(self):
        return 10.0

    def delay(self):
        return 2.5

    def gc(self):
        return 3.0

    def cooldown(self):
        return 5.0
    
    def duration(self):
        return randint(8,12)

    def prevented(self):
        for c in p.chars:
            if c.alive and not c.about_to_die and c.current_stance is not None and c.current_stance==ST_COUNTER_SLEEP:
                c.current_stance = None
                return True
            if c.alive and not c.about_to_die and c.current_stance is not None and c.current_stance==ST_COUNTER_MANY:
                c.current_stance = None
                if randint(1,100) <= min(70,success_chance(12-self.m.level)):
                    return True
                else:
                    d.message_queue.append(('Distraction failed',c.image,COL_LIGHT))
        return False

    def make_stuff_happen(self,target):
        d.sound_queue.append(S_BAD_SPELL)
        for c in p.chars:
            if c.alive and not c.about_to_die:
                immune = False
                for a in c.actions:
                    if a.action_code==CA_STEELY_GAZE:
                        immune = True
                        continue
                if c.has_status_effect(EF_MEDITATION):
                    immune = True
                if not immune:
                    c.status_effects[EF_SLEEP] = Effect_parameters(self.duration())
                    c.status_effects[EF_SLEEP].level = 3
                    c.action_in_progress = None
                    c.current_stance = None

    def get_description(self):
        return 'Makes your whole party fall asleep. Tap them three times to make them wake up. Can be countered with the "Counter: Sleep" spell.'
    
class Gaze_Drain_Bar_MA(Monster_action):
    def __init__(self):
        return Monster_action.__init__(self)
    
    def name(self):
        return 'Life Drain'

    def warning_message(self):
        return 'Gazes at you'

    def period(self):
        return 5.0

    def delay(self):
        return 3.5

    def target_enemy(self):
        return True

    def make_stuff_happen(self,target):
        d.sound_queue.append(S_BAD_SPELL)
        immune = False
        for a in target.actions:
            if a.action_code==CA_STEELY_GAZE:
                immune = True
                continue
        if not immune:
            target.energy = max(0,target.energy - randint(6,18))

    def get_description(self):
        return 'Drains the energy bar of one of your characters. Can be countered with the "Counter: Draining" spell.'

class Gaze_Paralysis_Often_MA(Gaze_Paralysis_MA):
    def __init__(self):
        return Monster_action.__init__(self)

    def period(self):
        return 5.0

    def delay(self):
        return 3.0

    def cooldown(self):
        return 2.0

    def gc(self):
        return 2.0
    
class Spit_MA(Monster_action):
    def __init__(self):
        return Monster_action.__init__(self)
    
    def name(self):
        return 'Spit'

    def warning_message(self):
        return 'Hoick...'

    def delay(self):
        return 2.5

    def period(self):
        return 10.0

    def gc(self):
        return 5.0

    def target_enemy(self):
        return True

    def deflectable(self):
        return True

    def prevented(self):
        for c in p.chars:
            if c.alive and not c.about_to_die and c.current_stance is not None and c.current_stance==ST_DEFLECT:
                c.current_stance = None
                return True
            if c.alive and not c.about_to_die and c.current_stance is not None and c.current_stance==ST_COUNTER_MANY:
                c.current_stance = None
                if randint(1,100) <= min(70,success_chance(12-self.m.level)):
                    return True
                else:
                    d.message_queue.append(('Distraction failed',c.image,COL_LIGHT))
        return False

    def make_stuff_happen(self,target):
        d.sound_queue.append(S_SPIT)
        if target.has_status_effect(EF_RESIST_POISON):
            return
        if target.has_status_effect(EF_POISON):
            poison_level = min(3,target.status_effects[EF_POISON].level + 2)
        else:
            poison_level = 2
        target.status_effects[EF_POISON] = Effect_parameters(10) 
        target.status_effects[EF_POISON].level = poison_level

    def get_description(self):
        return 'When it starts to spit at you, tell one of your front-row characters to use the Deflect ability or one of your characters will get poisoned. If you do get hit, use Cure Poison.'
    
class Smite_MA(Monster_action):
    # Orc warrior version
    def __init__(self):
        return Monster_action.__init__(self)
    
    def name(self):
        return 'Smite'

    def warning_message(self):
        return 'RAR!'

    def delay(self):
        return 3.0
 
    def period(self):
        return 10.0

    def gc(self):
        return self.delay()+1.0

    def back_row_ok(self):
        return False

    def target_enemy(self):
        return True

    def enemy_target_back_row_ok(self):
        return False

    def blockable(self):
        return True

    def prevented(self):
        for c in p.chars:
            if c.alive and not c.about_to_die and c.current_stance is not None and (c.current_stance==ST_BLOCK or c.current_stance==ST_LEAP_ASIDE):
                c.current_stance = None
                return True
            if c.alive and not c.about_to_die and c.current_stance is not None and c.current_stance==ST_EPIC_BLOCK:
                return True
            if c.alive and not c.about_to_die and c.current_stance is not None and c.current_stance==ST_COUNTER_MANY:
                c.current_stance = None
                if randint(1,100) <= min(70,success_chance(12-self.m.level)):
                    return True
                else:
                    d.message_queue.append(('Distraction failed',c.image,COL_LIGHT))
            if c.alive and not c.about_to_die and c.current_stance is not None and c.current_stance==ST_RIPOSTE:
                c.current_stance = None
                if randint(1,100) <= success_chance(c.daring + 3 - (self.m.health / 50)):
                    c.current_stance = ST_COUNTER
                    c.counter_against = self.m
                    return True
                else:
                    d.message_queue.append(('Failed to block',c.image,COL_LIGHT))
        return False

    def min_damage(self):
        return 10

    def max_damage(self):
        return 20
    
    def make_stuff_happen(self,target):
        d.sound_queue.append(S_SMITE)
        damage = randint(self.min_damage(),self.max_damage())
        target.take_damage(max(0,damage-target.current_armor()))

    def get_description(self):
        return 'When it says RAR, tell one of your front-row characters to use the Block ability or you will get hit.'

class Smite_Naga_MA(Smite_MA):
    def __init__(self):
        return Monster_action.__init__(self)
    
    def delay(self):
        return 3.0

    def period(self):
        return 12.0

    def min_damage(self):
        return 15

    def max_damage(self):
        return 30

class Smite_Black_Knight_MA(Smite_MA):
    def __init__(self):
        return Monster_action.__init__(self)
    
    def delay(self):
        return 2.0

    def period(self):
        return 7.0

    def gc(self):
        return 3.0

    def warning_message(self):
        return 'Take that!'

    def get_description(self):
        return 'When it says "Take That!", tell one of your front-row characters to use the Block ability or you will get hit.'
    
    def min_damage(self):
        return 20

    def max_damage(self):
        return 40

class Smite_Black_Ogre_MA(Smite_MA):
    def __init__(self):
        return Monster_action.__init__(self)
    
    def delay(self):
        return 2.5

    def period(self):
        return 8.0

    def min_damage(self):
        return 20

    def max_damage(self):
        return 40
  
class Smite_Dark_Priest_MA(Smite_Black_Knight_MA):
    def __init__(self):
        return Monster_action.__init__(self)
    
    def delay(self):
        return 3.0

    def period(self):
        return 12.0

    def min_damage(self):
        return 15

    def max_damage(self):
        return 30

class Smite_Berserker_MA(Smite_MA):
    def __init__(self):
        return Monster_action.__init__(self)
    
    def delay(self):
        return 2.0

    def period(self):
        return 5.0

    def min_damage(self):
        return 20

    def max_damage(self):
        return 40
    
class Smite_Ogre_MA(Smite_MA):
    def __init__(self):
        return Monster_action.__init__(self)
    
    def delay(self):
        return 3.0

    def period(self):
        return 6.0

    def min_damage(self):
        return 15

    def max_damage(self):
        return 30
    
class Smite_Orc_Boss_MA(Smite_MA):
    def __init__(self):
        return Monster_action.__init__(self)
    
    def delay(self):
        return 1.5

    def period(self):
        return 7.0

    def min_damage(self):
        return 20

    def max_damage(self):
        return 40

class Smite_Orc_Champion_MA(Smite_MA):
    def __init__(self):
        return Monster_action.__init__(self)
    
    def delay(self):
        return 2.0

    def period(self):
        return 8.0

    def min_damage(self):
        return 15

    def max_damage(self):
        return 30

class TailSwipe_MA(Smite_MA):
    def __init__(self):
        return Monster_action.__init__(self)
    
    def name(self):
        return 'Tail Swipe'

    def warning_message(self):
        return 'Readies tail...'

    def delay(self):
        return 2

    def period(self):
        return 5.0

    def back_row_ok(self):
        return True

    def front_row_ok(self):
        return False

    def min_damage(self):
        return 10

    def max_damage(self):
        return 20

    def get_description(self):
        return 'When it gets ready to swipe you, tell one of your front-row characters to use the Block ability or you will get hit.'

class Claw_MA(Smite_MA):
    def __init__(self):
        return Monster_action.__init__(self)
    
    def name(self):
        return 'Claw'

    def delay(self):
        return 2.5

    def period(self):
        return 3.0

    def gc(self):
        return 0.0

    def min_damage(self):
        return 12

    def max_damage(self):
        return 24

    def get_description(self):
        return 'When it gets ready to claw you, tell one of your front-row characters to use the Block ability or you will get hit.'

class Claw_Fast_MA(Claw_MA):
    def __init__(self):
        return Monster_action.__init__(self)
    
    def delay(self):
        return 1.5

class Shove_MA(Monster_action):
    def __init__(self):
        return Monster_action.__init__(self)
    
    def name(self):
        return 'Shove'

    def period(self):
        return 7.0

    def cooldown(self):
        return 5.0

    def back_row_ok(self):
        return False

    def target_enemy(self):
        return True

    def enemy_target_back_row_ok(self):
        return False

    def min_damage(self):
        return 6

    def max_damage(self):
        return 12

    def ok(self):
        return Monster_action.ok(self) and p.n_surviving_chars()>1
    
    def make_stuff_happen(self,target):
        d.sound_queue.append(S_SMITE)
        damage = randint(self.min_damage(),self.max_damage())
        target.take_damage(max(0,damage-target.current_armor()))
        if target.alive and not target.about_to_die:
            if target.pos==POS_C_FRONT_1:
                move_to = POS_C_BACK_1
            elif target.pos==POS_C_FRONT_2:
                move_to = POS_C_BACK_2
            elif target.pos==POS_C_FRONT_3:
                move_to = POS_C_BACK_3
            displaced = p.get_char_at_pos(move_to)
            if displaced is not None:
                if p.last_free_back_pos() is not None:
                    displaced.slide_from = displaced.pos
                    displaced.pos = p.last_free_back_pos()
                    displaced.slide_to = displaced.pos
                    displaced.slide_start = w.elapsed_since_beginning
                    displaced.slide_finish = w.elapsed_since_beginning + 0.1
                else:
                    displaced.slide_from = displaced.pos
                    displaced.pos = target.pos
                    displaced.slide_to = displaced.pos
                    displaced.slide_start = w.elapsed_since_beginning
                    displaced.slide_finish = w.elapsed_since_beginning + 0.1
            target.slide_from = target.pos
            target.pos = move_to
            target.slide_to = move_to
            target.slide_start = w.elapsed_since_beginning
            target.slide_finish = w.elapsed_since_beginning + 0.1
            
    def get_description(self):
        return 'Pushes one of your front row characters into the back row.'

class Tornado_MA(Monster_action):
    def __init__(self):
        return Monster_action.__init__(self)
    
    def name(self):
        return 'Tornado'

    def period(self):
        return 10.0

    def cooldown(self):
        return 5.0

    def likelihood_increase_until_n_uses(self):
        return 3.0

    def n_uses(self):
        return 1

    def gc(self):
        return 2.0

    def min_damage(self):
        return 2

    def max_damage(self):
        return 6
   
    def make_stuff_happen(self,target):
        d.sound_queue.append(S_WIND)
        for c in p.chars:
            if c.alive and not c.about_to_die:
                c.take_damage(randint(self.min_damage(),self.max_damage()))
                c.current_stance = None
                c.action_in_progress = None
        posses_to_take = 0
        for c in p.chars:
            if c.pos is not None and c.alive:
                posses_to_take = posses_to_take + 1
        while 1:
            posses_taken = sample([POS_C_FRONT_1,POS_C_FRONT_2,POS_C_FRONT_3,POS_C_BACK_1,POS_C_BACK_2,POS_C_BACK_3],posses_to_take)
            if ((POS_C_FRONT_1 in posses_taken) or (POS_C_FRONT_2 in posses_taken) or (POS_C_FRONT_3 in posses_taken)):
                break
        count = 0
        for c in p.chars:
            if c.alive:
                c.slide_from = c.pos
                c.slide_to = posses_taken[count]
                c.pos = c.slide_to
                c.slide_start = w.elapsed_since_beginning
                c.slide_finish = w.elapsed_since_beginning + 0.1
                count = count + 1
            
    def get_description(self):
        return 'The swirling winds push all your characters out of position, as well as damaging them. There is no defense.'

class Enter_Shell_MA(Monster_action):
    def __init__(self):
        return Monster_action.__init__(self)

    def name(self):
        return 'Ducks into its shell'

    def period(self):
        return 6.0

    def cooldown(self):
        return 10.0

    def ok(self):
        if self.m.invulnerable_until == 99999:
            return False
        else:
            return Monster_action.ok(self)
        
    def likelihood_increase_until_n_uses(self):
        return 2

    def n_uses(self):
        return 1
    
    def make_stuff_happen(self,target):
        self.m.invulnerable_until = 99999
        self.m.fire_resistant = True

    def get_description(self):
        return 'Protects it from most kinds of damage. It can still attack you, however, with its heavily armored forepaws.'

class Exit_Shell_MA(Monster_action):
    def __init__(self):
        return Monster_action.__init__(self)

    def name(self):
        return 'Emerges from its shell'

    def period(self):
        if e.n_surviving_mobs()==1:
            return 3.0
        else:
            return 6.0

    def ok(self):
        if self.m.invulnerable_until == 0:
            return False
        else:
            return Monster_action.ok(self)
        
    def make_stuff_happen(self,target):
        self.m.invulnerable_until = 0
        self.m.fire_resistant = False

    def get_description(self):
        return 'Makes it briefly vulnerable to normal damage.'

class Anticaster_MA(Monster_action):
    def __init__(self):
        return Monster_action.__init__(self)
    
    def name(self):
        return 'Zap'

    def period(self):
        return 1.0
    
    def ok(self):
        for c in p.chars:
            if c.alive and not c.about_to_die and c.action_in_progress is not None and c.action_in_progress.disruptable():
                return Monster_action.ok(self)
        return False
    
    def min_damage(self):
        return 2

    def max_damage(self):
        return 6

    def make_stuff_happen(self,target):
        for c in p.chars:
            if c.alive and not c.about_to_die and c.action_in_progress is not None and c.action_in_progress.disruptable():               
                d.sound_queue.append(S_BAD_SPELL)
                damage = randint(self.min_damage(),self.max_damage())
                c.take_damage(damage)
                return

    def get_description(self):
        return 'Zaps one of your casters just as they start to cast a spell. Armor does protect you, and may even allow you to complete the spell.'

class Firebolt_MA(Monster_action):
    def __init__(self):
        return Monster_action.__init__(self)
    
    def name(self):
        return 'Throw Fire'

    def warning_message(self):
        return 'Flares up'

    def bolt(self):
        return True

    def delay(self):
        return 2.0

    def period(self):
        return 6.0

    def gc(self):
        return self.delay()+1

    def min_damage(self):
        return 10

    def max_damage(self):
        return 20

    def fiery(self):
        return True

    def target_enemy(self):
        return True

#    def enemy_target_back_row_ok(self):
#        return False

    def prevented(self):
        for c in p.chars:
            if c.alive and not c.about_to_die and c.current_stance is not None and c.current_stance==ST_COUNTER_BOLTS:
                c.current_stance = None
                return True
            if c.alive and not c.about_to_die and c.current_stance is not None and c.current_stance==ST_COUNTER_MANY:
                c.current_stance = None
                if randint(1,100) <= min(70,success_chance(12-self.m.level)):
                    return True
                else:
                    d.message_queue.append(('Distraction failed',c.image,COL_LIGHT))
        return False

    def sound(self):
        return S_FIRE_BOLT

    def make_stuff_happen(self,target):
        d.sound_queue.append(self.sound())
        damage = randint(self.min_damage(),self.max_damage())
        if target.has_status_effect(EF_RESIST_FIRE):
            damage = damage / 2
        target.take_damage(damage)

    def get_description(self):
        return 'Burns one of your front row characters. Armour is no protection, but fire resistance halves damage. Can be countered with the Reflection spell.'

class Zap_MA(Firebolt_MA):
    def __init__(self):
        return Monster_action.__init__(self)

    def period(self):
        return 4.0
    
    def name(self):
        return 'Zap'

    def delay(self):
        return 1.0
    
    def min_damage(self):
        return 2

    def max_damage(self):
        return 6

    def sound(self):
        return S_ZAP

class Darkness_MA(Monster_action):
    def __init__(self):
        return Monster_action.__init__(self)

    def name(self):
        return 'Darkness'

    def period(self):
        return 5.0

    def likelihood_increase_until_n_uses(self):
        return 3.0

    def n_uses(self):
        return 1

    def make_stuff_happen(self,target):
       d.sound_queue.append(S_BAD_SPELL)
       w.darkness_at = w.elapsed_since_beginning
       w.darkness = w.darkness + 1

    def get_description(self):
        return 'Darkens the screen. Stacks cumulatively, but fortunately wears off after a while - or use the Light spell.'

class Silence_MA(Monster_action):
    def __init__(self):
        return Monster_action.__init__(self)

    def name(self):
        return 'Silent Aura'

    def warning_message(self):
        return 'Gestures mutely'

    def silencing(self):
        return True

    def period(self):
        return 6.0

    def cooldown(self):
        return 8.0

    def gc(self):
        return 3.0

    def likelihood_increase_until_n_uses(self):
        return 2.0

    def delay(self):
        return 2.5

    def n_uses(self):
        return 1

    def prevented(self):
        for c in p.chars:
            if c.alive and not c.about_to_die and c.current_stance is not None and c.current_stance==ST_COUNTER_SILENCE:
                c.current_stance = None
                return True
            if c.alive and not c.about_to_die and c.current_stance is not None and c.current_stance==ST_COUNTER_MANY:
                c.current_stance = None
                if randint(1,100) <= min(70,success_chance(12-self.m.level)):
                    return True
                else:
                    d.message_queue.append(('Distraction failed',c.image,COL_LIGHT))
        return False

    def make_stuff_happen(self,target):
       w.silence_until = w.elapsed_since_beginning + randint(6,12)
       for c in p.chars:
           if c.alive and not c.about_to_die and c.action_in_progress is not None and c.action_in_progress.disruptable():
               c.action_in_progress = None
               d.message_queue.append(('Action disrupted',c.image,COL_LIGHT))

    def get_description(self):
        return 'Creates a magical silence, preventing your characters from casting any spells. Can be countered with the "Counter: Silence" spell.'

class Breath_Fire_MA(Monster_action):
    def __init__(self):
        return Monster_action.__init__(self)
    
    def name(self):
        return 'Breathes fire!!'

    def warning_message(self):
        return 'Inhales...'

    def delay(self):
        return 4.0

    def period(self):
        return 10.0

    def likelihood_increase_until_n_uses(self):
        return 5.0

    def n_uses(self):
        return 1

    def cooldown(self):
        return 15.0

    def gc(self):
        return 10.0

    def front_row_ok(self):
        return False

    def fiery(self):
        return True

    def take_coverable(self):
        return True

    def damroll(self):
        return randint(30,50)
    
    def make_stuff_happen(self,target):
       d.sound_queue.append(S_BREATH)
       for c in p.chars:
           if c.alive and not c.about_to_die:
               damage = self.damroll()
               if c.current_stance==ST_TAKE_COVER and randint(1,100)<=p.take_cover_success_chance:
                   pass
               else:
                   if c.has_status_effect(EF_RESIST_FIRE):
                       damage = damage / 2
                   c.take_damage(damage)

    def get_description(self):
        return 'Fries your entire party. Can be countered with the "Take Cover!" ability, though this is not 100% reliable, or the "Extinguish" spell. Fire resistance halves damage.'

class Breath_Small_Fire_MA(Breath_Fire_MA):
    def __init__(self):
        return Monster_action.__init__(self)

    def back_row_ok(self):
        return False

    def front_row_ok(self):
        return True
    
    def delay(self):
        return 3.0

    def period(self):
        return 10.0

    def likelihood_increase_until_n_uses(self):
        return 2.0

    def cooldown(self):
        return 10.0

    def gc(self):
        return 3.0

    def damroll(self):
        return randint(10,30)

    def make_stuff_happen(self,target):
       d.sound_queue.append(S_BREATH)
       for c in p.chars:
           if c.alive and not c.about_to_die and c.front_row():
               damage = self.damroll()
               if c.current_stance==ST_TAKE_COVER and randint(1,100)<=p.take_cover_success_chance:
                   pass
               else:
                   if c.has_status_effect(EF_RESIST_FIRE):
                       damage = damage / 2
                   c.take_damage(damage)

    def get_description(self):
        return 'Fries all your front-row characters. Can be countered with the "Take Cover!" ability, though this is not 100% reliable, or the "Extinguish" spell. Fire resistance halves damage.'

class Flit_MA(Monster_action):
    def __init__(self):
        return Monster_action.__init__(self)

    def period(self):
        return 3.0

#    def front_row_ok(self):
#        return False

    def ok(self):
        if (not self.m.front_row()) and e.last_free_back_pos() is None:
            return False
        if self.m.front_row() and e.last_free_front_pos() is None:
            return False
        return Monster_action.ok(self)

    def make_stuff_happen(self,target):
        if e.last_free_back_pos() is not None:
            slide_to = None
            count = 0
            while (slide_to is None and count <= 3):
                if self.m.front_row():
                    slide_to = e.random_free_front_pos()
                else:
                    slide_to = e.random_free_back_pos()
                if not adjacent(slide_to,self.m.pos):
                    slide_to = None
                count = count + 1
            if slide_to is not None:
                self.m.slide_from = self.m.pos
                self.m.pos = slide_to
                self.m.slide_to = self.m.pos
                self.m.slide_start = w.elapsed_since_beginning
                self.m.slide_finish = w.elapsed_since_beginning + 0.1
                    
class Flit_Taunt_MA(Monster_action):
    def __init__(self):
        return Monster_action.__init__(self)

    def period(self):
        return 3.0

    def front_row_ok(self):
        return False

    def ok(self):
        if e.last_free_back_pos() is None:
            return False
        return Monster_action.ok(self)
    
    def messages(self):
        return ['Arrk!','Screech!','You lousy wretch!','You\'ll feel my claws soon!','I\'ll pick your corpse!']

    def make_stuff_happen(self,target):
        if e.last_free_back_pos() is not None:
            slide_to = None
            count = 0
            while (slide_to is None and count <= 3):
                slide_to = e.random_free_back_pos()
                if not adjacent(slide_to,self.m.pos):
                    slide_to = None
                count = count + 1
            if slide_to is not None:
                self.m.slide_from = self.m.pos
                self.m.pos = slide_to
                self.m.slide_to = self.m.pos
                self.m.slide_start = w.elapsed_since_beginning
                self.m.slide_finish = w.elapsed_since_beginning + 0.1
                message = choice(self.messages())
                d.message_queue.append(('"'+message+'"',self.m.image,COL_LIGHT))

class Dwarf_Spirits_MA(Monster_action):
    def __init__(self):
        return Monster_action.__init__(self)

    def name(self):
        return 'Quaffs Dwarf Spirits'

    def period(self):
        return 12.0

    def cooldown(self):
        return 6.0

    def make_stuff_happen(self,target):
        d.sound_queue.append(S_POTION)
        self.m.health = min(self.m.max_health,self.m.health + randint(5,15))

    def get_description(self):
        return 'Heals the dipsomaniac Dwarf.'

class Soft_Enrage_MA(Monster_action):
    def __init__(self):
        return Monster_action.__init__(self)

    def name(self):
        return 'Rage!'

    def period(self):
        return 10.0

    def cooldown(self):
        return 5.0

    def fiery(self):
        return True 

    def make_stuff_happen(self,target):
        d.sound_queue.append(S_BREATH)
        self.m.health = min(self.m.max_health,self.m.health + randint(15,35))
        self.m.enrage = self.m.enrage + 1

    def get_description(self):
        return 'Heals itself and increases its damage. Kill it before it enrages too many times and becomes unstoppable! Or use the "Extinguish" spell.'

class Flee_MA(Monster_action):
    def __init__(self):
        return Monster_action.__init__(self)

    def period(self):
        return 5.0

    def make_stuff_happen(self,target):
        if w.elapsed_since_beginning > (self.m.arrived + 20):
            self.m.flee()

class Flee_Fast_MA(Monster_action):
    def __init__(self):
        return Monster_action.__init__(self)

    def period(self):
        return 5.0

    def make_stuff_happen(self,target):
        if w.elapsed_since_beginning > (self.m.arrived + 5):
            self.m.flee()

class Flee_Slowly_MA(Monster_action):
    def __init__(self):
        return Monster_action.__init__(self)

    def period(self):
        return 25.0

    def make_stuff_happen(self,target):
        if w.elapsed_since_beginning > (self.m.arrived + 40):
            self.m.flee()

class Irish_MA(Monster_action):
    def __init__(self):
        return Monster_action.__init__(self)

    def period(self):
        return 9.0

    def likelihood_increase_until_n_uses(self):
        return 3.0

    def cooldown(self):
        return 3.0

    def n_uses(self):
        return 3
    
    def messages(self):
        return ['Faith and begorrah!','Grand altogether!','Top \'o the morning!','Ye\'ll niver get me gold!','Slainte!']

    def make_stuff_happen(self,target):
        message = choice(self.messages())
        d.message_queue.append(('"'+message+'"',self.m.image,COL_LIGHT))

class Argot_MA(Irish_MA):
    def __init__(self):
        return Monster_action.__init__(self)
   
    def messages(self):
        return ['I\'ll cut your painter for ye!','I\'ll lend ye a polt in the muns!','Sowr the cull!','I\'ll tip him a jordain!','Let\'s kimbaw the cove!','Away to the boozing-ken!','I\'ll file a cly!','The swell is well breeched!']

class Scots_MA(Irish_MA):
    def __init__(self):
        return Monster_action.__init__(self)

    def period(self):
        return 18.0

    def likelihood_increase_until_n_uses(self):
        return 3.0

    def n_uses(self):
        return 3

    def messages(self):
        return ['I\'ll gie ye a skelpit lug!','Ye wee bampot!','Ah\'m no fooling ye!','Here\'s a Glasgow kiss fer ye!','Yese a minger, so ye is!','Ah, Goad! Ah\'m steamin']

class Fly_MA(Monster_action):
    def __init__(self):
        return Monster_action.__init__(self)
    
    def period(self):
        return 10.0

    def gc(self):
        return 1.0

    def back_row_ok(self):
        return False

    def make_stuff_happen(self,target):
        if e.last_free_back_pos() is None:
            e.push_monster_forward(self.m.level)
        if e.last_free_back_pos() is not None:
            self.m.slide_from = self.m.pos
            self.m.pos = e.random_free_back_pos()
            self.m.slide_to = self.m.pos
            self.m.slide_start = w.elapsed_since_beginning
            self.m.slide_finish = w.elapsed_since_beginning + 0.1
            self.m.move_to_front_at = w.elapsed_since_beginning + 15

class Advance_MA(Monster_action):
    def __init__(self):
        return Monster_action.__init__(self)

    def period(self):
        return 5.0

    def front_row_ok(self):
        return False

    def ok(self):
        if e.last_free_front_pos() is None:
            return False
        else:
            return Monster_action.ok(self)

    def make_stuff_happen(self,target):
        if e.last_free_front_pos() is not None:
            self.m.slide_from = self.m.pos
            self.m.pos = e.random_free_front_pos()
            self.m.slide_to = self.m.pos
            self.m.slide_start = w.elapsed_since_beginning
            self.m.slide_finish = w.elapsed_since_beginning + 0.1

class Hurl_And_Advance_MA(Monster_action):
    def __init__(self):
        return Monster_action.__init__(self)

    def name(self):
        return 'Hurl Boulder'

    def warning_message(self):
        return 'Picks up a big rock'

    def delay(self):
        return 2.0

    def period(self):
        return 3.0

    def cooldown(self):
        return 10.0

    def likelihood_increase_until_n_uses(self):
        return 2.0

    def n_uses(self):
        return 1

    def front_row_ok(self):
        return False

    def take_coverable(self):
        return True

    def leap_awayable(self):
        return True

    def make_stuff_happen(self,target):
        self.m.move_to_front_at = w.elapsed_since_beginning + 1
        target = p.random_front_target(self.m)
        if target is not None:
            if target.current_stance==ST_LEAP_ASIDE:
                target.current_stance = None
                d.sound_queue.append(S_MONMISS)
                d.message_queue.append(('Missed!',self.m.image,COL_LIGHT))
                return
            if target.current_stance==ST_TAKE_COVER:
                for c in p.chars:
                    c.current_stance = None
                if randint(1,100)<=p.take_cover_success_chance:
                    d.sound_queue.append(S_MONMISS)
                    d.message_queue.append(('Missed!',self.m.image,COL_LIGHT))
                    return
            if randint(1,20)<=2*(8 - target.current_evasion()):
                d.sound_queue.append(S_SMITE)
                damage = randint(10,30)
                armor = target.current_armor()
                d.message_queue.append(('Ouch!',target.image,COL_LIGHT))
                if damage > armor:
                    target.take_damage(max(0,damage-armor))
            else:
                d.message_queue.append(('Missed!',self.m.image,COL_LIGHT))
                d.sound_queue.append(S_MONMISS)

    def get_description(self):
        return 'Throws a heavy rock at you and then advances to the front row. The rock can be countered with the "Leap Aside" ability - or the "Take Cover!" ability, though this is not reliable.'

class Swoop_MA(Monster_action):
    def __init__(self):
        return Monster_action.__init__(self)

    def message(self):
        return ''

    def period(self):
        return 5.0

    def front_row_ok(self):
        return False

    def ok(self):
        if e.last_free_front_pos() is None:
            return False
        else:
            return Monster_action.ok(self)

    def make_stuff_happen(self,target):
        if e.last_free_front_pos() is not None:
            self.m.slide_from = self.m.pos
            self.m.pos = e.random_free_front_pos()
            self.m.slide_to = self.m.pos
            self.m.slide_start = w.elapsed_since_beginning
            self.m.slide_finish = w.elapsed_since_beginning + 0.1
            target = p.random_front_target(self.m)
            if target is not None:
                if self.m.melee_hit_roll(target):
                    d.sound_queue.append(self.m.melee_hit_sound)
                    damage = self.m.melee_damage_roll()
                    armor = target.current_armor()
                    if damage > armor:
                        target.take_damage(max(0,damage-armor))
                else:
                    d.sound_queue.append(self.m.melee_miss_sound)
            self.m.move_to_back_at = w.elapsed_since_beginning + 4

    def get_description(self):
        return 'Can move forwards, attack you immediately, and soon return to the back row.'

class Explode_MA(Monster_action):
    def __init__(self):
        return Monster_action.__init__(self)
    
    def name(self):
        return 'Explode!!'

    def period(self):
        return 5.0

    def gc(self):
        return 2.0

    def back_row_ok(self):
        return False

    def take_coverable(self):
        return True
    
    def make_stuff_happen(self,target):
       d.sound_queue.append(S_BREATH)
       for c in p.chars:
           if c.alive and not c.about_to_die and c.front_row():
               if c.current_stance==ST_TAKE_COVER and randint(1,100)<=p.take_cover_success_chance:
                   pass
               else:
                   c.take_damage(randint(10,20))
                   if c.alive and not c.about_to_die:
                       c.status_effects[EF_STUN] = Effect_parameters(2.0)                    
       self.m.die()

    def get_description(self):
        return 'Damages your whole front row. Can be countered with the "Take Cover!" ability, though this is not 100% reliable.'

class Aura_Fire_MA(Monster_action):
    def __init__(self):
        return Monster_action.__init__(self)
    
    def name(self):
        return 'Fiery Aura'

    def period(self):
        return 3.0

    def cooldown(self):
        return 2.0

    def back_row_ok(self):
        return False

    def fiery(self):
        return True

    def min_damage(self):
        return 4

    def max_damage(self):
        return 8

    def make_stuff_happen(self,target):
        for pos in [POS_C_FRONT_1,POS_C_FRONT_2,POS_C_FRONT_3]:
            c = p.get_char_at_pos(pos)
            if c is not None and c.alive and not c.about_to_die:
                damage = randint(self.min_damage(),self.max_damage())
                if c.has_status_effect(EF_RESIST_FIRE):
                    damage = damage/2
                c.take_damage(damage)
        for pos in [POS_M_FRONT_1,POS_M_FRONT_2,POS_M_FRONT_3]:
            m = e.get_monster_at_pos(pos)
            if m is not None and m.alive and not m.about_to_die and not m.fire_resistant:
                damage = randint(self.min_damage(),self.max_damage())
                m.take_damage(damage)

    def get_description(self):
        return 'Burns all front-row characters. Cannot be prevented, though fire resistance helps. Can also damage front-row monsters.'

class Aura_Fire_Rage_MA(Aura_Fire_MA):
    def __init__(self):
        return Monster_action.__init__(self)

    def period(self):
        return max(2,5-self.m.enrage)

    def min_damage(self):
        return 2 + self.m.enrage

    def max_damage(self):
        return 2 * self.min_damage()    

class Aura_Cold_MA(Aura_Fire_MA):
    def __init__(self):
        return Monster_action.__init__(self)
    
    def name(self):
        return 'Frigid Aura'    

    def fiery(self):
        return False

    def freezy(self):
        return True

    def damroll(self):
        return randint(4,8)

    def make_stuff_happen(self,target):
        for pos in [POS_C_FRONT_1,POS_C_FRONT_2,POS_C_FRONT_3]:
            c = p.get_char_at_pos(pos)
            if c is not None and c.alive and not c.about_to_die:
                damage = self.damroll()
                if c.has_status_effect(EF_RESIST_COLD):
                    damage = damage/2
                c.take_damage(damage)
        for pos in [POS_M_FRONT_1,POS_M_FRONT_2,POS_M_FRONT_3]:
            m = e.get_monster_at_pos(pos)
            if m is not None and m.alive and not m.about_to_die and not m.cold_resistant:
                m.take_damage(self.damroll())

    def get_description(self):
        return 'Damages all front-row characters. Cannot be prevented. Can also damage front-row monsters.'

class Aura_Cool_MA(Aura_Cold_MA):
    def __init__(self):
        return Monster_action.__init__(self)

    def period(self):
        return 4.0
    
    def damroll(self):
        return randint(2,3)

class Block_MA(Monster_action):
    def __init__(self):
        return Monster_action.__init__(self)

    def name(self):
        return 'Shield Block'
    
    def ok(self):
        for c in p.chars:
            if c.alive and not c.about_to_die and c.action_in_progress is not None and (c.action_in_progress.action_code==CA_SMITE or c.action_in_progress.action_code==CA_BASH):
                return Monster_action.ok(self)
        return False

    def period(self):
        return 0.1

    def make_stuff_happen(self,target):
        for c in p.chars:
            if c.alive and not c.about_to_die and c.action_in_progress is not None and (c.action_in_progress.action_code==CA_SMITE or c.action_in_progress.action_code==CA_BASH):
                c.action_in_progress = None
                return

    def get_description(self):
        return 'Blocks your Bash and Smite attacks.'

class Catch_MA(Monster_action):
    def __init__(self):
        return Monster_action.__init__(self)

    def name(self):
        return 'Hooks you'

    def ok(self):
        for c in p.chars:
            if c.alive and not c.about_to_die and c.grappled_by==self.m:
                return False
        return Monster_action.ok(self)

    def period(self):
        return 7.0

    def cooldown(self):
        return 7.0

    def gc(self):
        return 2.0

    def target_enemy(self):
        return True

    def enemy_target_back_row_ok(self):
        return False

    def back_row_ok(self):
        return False

    def duration(self):
        return float(randint(6,12))

    def make_stuff_happen(self,target):
        if (randint(1,100)<=success_chance(target.current_evasion())):
            d.message_queue.append(('Avoids the grappling attack!',target.image,COL_LIGHT))
        else:
            d.sound_queue.append(S_BAD_SPELL)
            if target.alive and not target.about_to_die:
                target.status_effects[EF_GRAPPLE] = Effect_parameters(self.duration())
                target.action_in_progress = None
                target.current_stance = None
                target.grappled_by = self.m

    def get_description(self):
        return 'One of your front row characters is caught, cannot move away, and is vulnerable to the Deathblow attack.'

class Grapple_MA(Catch_MA):
    def __init__(self):
        return Monster_action.__init__(self)

    def name(self):
        return 'Grapples you'

    def duration(self):
        return float(randint(100,200))

    def get_description(self):
        return 'One of your front row characters is caught, cannot move away, and will take damage steadily until you kill this monster.'

class Finishing_Blow_MA(Monster_action):
    def __init__(self):
        return Monster_action.__init__(self)

    def name(self):
        return 'Coup de grace!'

    def warning_message(self):
        return 'Prepares to strike a helpless character'
    
    def finishing(self):
        return True

    def period(self):
        return 4.0

    def cooldown(self):
        return 8.0

    def gc(self):
        return 2.0

    def delay(self):
        return 2.0
    
    def gc(self):
        return 3.0

    def back_row_ok(self):
        return False

    def ok(self):
        for c in p.chars:
            if c.alive and not c.about_to_die and c.front_row():
                if c.has_status_effect(EF_GRAPPLE) or c.has_status_effect(EF_STUN) or c.has_status_effect(EF_SLEEP):
                    return Monster_action.ok(self)
        return False

    def prevented(self):
        for c in p.chars:
            if c.alive and not c.about_to_die and c.current_stance is not None and c.current_stance==ST_COUNTER_DEATHBLOW:
                c.current_stance = None
                return True
            if c.alive and not c.about_to_die and c.current_stance is not None and c.current_stance==ST_COUNTER_MANY:
                c.current_stance = None
                if randint(1,100) <= min(70,success_chance(12-self.m.level)):
                    return True
                else:
                    d.message_queue.append(('Distraction failed',c.image,COL_LIGHT))
        return False

    def make_stuff_happen(self,target):
        choices = []
        for c in p.chars:
            if c.alive and not c.about_to_die and c.front_row():
                if c.has_status_effect(EF_GRAPPLE) or c.has_status_effect(EF_STUN) or c.has_status_effect(EF_SLEEP):
                    choices.append(c)
        if len(choices)>0:
            target = choice(choices)
            d.sound_queue.append(S_SMITE)
            damage = randint(40,60)
            target.take_damage(damage-target.current_armor())

    def get_description(self):
        return 'Mortally wounds - or kills outright - a grappled, stunned, sleeping, constricted or hooked character. Can be countered with the "Protect the Fallen" ability.'

class Stun_MA(Smite_MA):
    def __init__(self):
        return Monster_action.__init__(self)

    def name(self):
        return 'Stunning Blow'

    def warning_message(self):
        return 'Menaces you'

    def delay(self):
        return 1.5

    def gc(self):
        return self.delay() + 1
    
    def period(self):
        return 10.0

    def min_damage(self):
        return 10

    def max_damage(self):
        return 20
    
    def make_stuff_happen(self,target):
        d.sound_queue.append(S_SMITE)
        damage = randint(self.min_damage(),self.max_damage())
        target.take_damage(max(0,damage-target.current_armor()))
        if target.has_status_effect(EF_MEDITATION):
            return
        target.status_effects[EF_STUN] = Effect_parameters(self.duration())
        target.action_in_progress = None
        target.current_stance = None

    def duration(self):
        return randint(6,12)
    
    def get_description(self):
        return 'When it menaces you, tell one of your front-row characters to use the Block ability or you will get stunned.'
    
class Squeeze_MA(Monster_action):
    def __init__(self):
        return Monster_action.__init__(self)

    def period(self):
        return 0.7

    def cooldown(self):
        return 0.5

    def back_row_ok(self):
        return False

    def ok(self):
        for c in p.chars:
            if c.alive and not c.about_to_die and c.front_row():
                if c.has_status_effect(EF_GRAPPLE) and c.grappled_by==self.m:
                    return Monster_action.ok(self)
        return False

    def make_stuff_happen(self,target):
        choices = []
        for c in p.chars:
            if c.alive and not c.about_to_die and c.front_row():
                if c.has_status_effect(EF_GRAPPLE) and c.grappled_by==self.m:
                    choices.append(c)
        if len(choices)>0:
            target = choice(choices)
            target.take_damage(2)

class Spell_Deflect_Missiles_MA(Monster_action):
    def __init__(self):
        return Monster_action.__init__(self)

    def name(self):
        return 'Deflect Missiles'
    
    def period(self):
        return 0.4

    def ok(self):
        if self.m.has_been_shot and self.m.ranged_only_evasion==0:
            return Monster_action.ok(self)
        else:
            return False

    def make_stuff_happen(self,target):
        self.m.ranged_only_evasion = 6

    def get_description(self):
        return 'Makes the caster harder to hit with ranged attacks. The "Mage Bane" ability is helpful here.'
    
class Monster_Spell_MA(Monster_action):
    def __init__(self):
        return Monster_action.__init__(self)

    def disruptable(self):
        return True

    def delay(self):
        return 2.5

    def gc(self):
        return self.delay() + 1

    def warning_message(self):
        return 'Begins to cast ' + self.name()

    def period(self):
        return 8.0

    def cooldown(self):
        return 3.0
   
class Spell_Cause_Wounds_MA(Monster_Spell_MA):
    def __init__(self):
        return Monster_action.__init__(self)

    def name(self):
        return 'Cause Wounds'

    def target_enemy(self):
        return True
   
    def min_damage(self):
        return 6

    def max_damage(self):
        return 12
        
    def make_stuff_happen(self,target):
        d.sound_queue.append(S_BAD_SPELL)
        damage = randint(self.min_damage(),self.max_damage())
        target.take_damage(damage)

    def get_description(self):
        return 'Harms one of your characters. There is no defense.'
    
class Spell_Firebolt_MA(Monster_Spell_MA):
    def __init__(self):
        return Monster_action.__init__(self)

    def name(self):
        return 'Firebolt'

    def bolt(self):
        return True

    def fiery(self):
        return True

    def target_enemy(self):
        return True

#    def enemy_target_back_row_ok(self):
#        return False
    
    def min_damage(self):
        return 10

    def max_damage(self):
        return 20
        
    def prevented(self):
        for c in p.chars:
            if c.alive and not c.about_to_die and c.current_stance is not None and c.current_stance==ST_COUNTER_BOLTS:
                c.current_stance = None
                return True
            if c.alive and not c.about_to_die and c.current_stance is not None and c.current_stance==ST_COUNTER_MANY:
                c.current_stance = None
                if randint(1,100) <= min(70,success_chance(12-self.m.level)):
                    return True
                else:
                    d.message_queue.append(('Distraction failed',c.image,COL_LIGHT))
        return False

    def make_stuff_happen(self,target):
        d.sound_queue.append(S_FIRE_BOLT)
        damage = randint(self.min_damage(),self.max_damage())
        if target.has_status_effect(EF_RESIST_FIRE):
            damage = damage / 2
        target.take_damage(damage)

    def get_description(self):
        return 'Burns one of your characters. Armour is no protection, but fire resistance halves damage. Can be countered with the "Reflection" spell.'
    
class Spell_Heal_MA(Monster_Spell_MA):
    def __init__(self):
        return Monster_action.__init__(self)

    def name(self):
        return 'Healing'

    def ok(self):
        if Monster_action.ok(self):
            for m in e.mobs:
                if m.alive and not m.about_to_die and m.health < (m.max_health - 10):
                    return True
        return False
        
    def make_stuff_happen(self,target):
        choices = []
        for m in e.mobs:
            if m.alive and not m.about_to_die and m.health < (m.max_health - 10):
                choices.append(m)
        if len(choices)>0:
            target = choice(choices)
            d.sound_queue.append(S_HEALING)
            old_health = target.health
            target.health = min(target.max_health,target.health + randint(15,25))
            target.flash_number = target.health - old_health
            target.number_starts = w.elapsed_since_beginning
            target.number_color = [0, 255, 0]
            
    def get_description(self):
        return 'Heals an injured monster.'

class Spell_Lightning_MA(Monster_Spell_MA):
    def __init__(self):
        return Monster_action.__init__(self)

    def name(self):
        return 'Lightning Bolt'

    def bolt(self):
        return True

    def target_enemy(self):
        return True

 #   def enemy_target_back_row_ok(self):
 #       return False
    
    def min_damage(self):
        return 25

    def max_damage(self):
        return 50

    def take_coverable(self):
        return True
    
    def prevented(self):
        for c in p.chars:
            if c.alive and not c.about_to_die and c.current_stance is not None and c.current_stance==ST_COUNTER_BOLTS:
                c.current_stance = None
                return True
            if c.alive and not c.about_to_die and c.current_stance is not None and c.current_stance==ST_COUNTER_MANY:
                c.current_stance = None
                if randint(1,100) <= min(70,success_chance(12-self.m.level)):
                    return True
                else:
                    d.message_queue.append(('Distraction failed',c.image,COL_LIGHT))
        return False

    def make_stuff_happen(self,target):
        if target.current_stance==ST_TAKE_COVER:
            if randint(1,100)<=p.take_cover_success_chance:
                d.sound_queue.append(S_MONMISS)
                return
        d.sound_queue.append(S_LIGHTNING_BOLT)
        damage = randint(self.min_damage(),self.max_damage())
        target.take_damage(damage)

    def get_description(self):
        return 'Zaps one of your characters. Armour is no protection. Can be countered with the "Reflection" spell, or the "Take Cover" ability (though this is not reliable).'

class Spell_Chill_Dart_MA(Spell_Lightning_MA):
    def __init__(self):
        return Monster_action.__init__(self)

    def name(self):
        return 'Chill Dart'

    def delay(self):
        return 1.0

    def back_row(self):
        return False

    def enemy_target_back_row_ok(self):
        return True
    
    def enemy_target_front_row_ok(self):
        return False
    
    def take_coverable(self):
        return False

    def freezy(self):
        return True

    def min_damage(self):
        return 6

    def max_damage(self):
        return 12

    def duration(self):
        return randint(1,2)
    
    def make_stuff_happen(self,target):
        d.sound_queue.append(S_BAD_SPELL)
        damage = randint(self.min_damage(),self.max_damage())
        if target.has_status_effect(EF_RESIST_COLD):
            damage = damage / 2
        target.take_damage(damage)
        if target.alive and not target.about_to_die and not (target.has_status_effect(EF_RESIST_COLD)):
            target.status_effects[EF_PARALYSIS] = Effect_parameters(self.duration())
            target.action_in_progress = None
            target.current_stance = None
            
    def get_description(self):
        return 'Zaps one of your back row characters, from the enemy front row. Armour is no protection. Can be countered with the "Reflection" spell, though you have to be very quick. Cold resistance also helps.'

class Spell_Shadow_Bolt_MA(Spell_Lightning_MA):
    def __init__(self):
        return Monster_action.__init__(self)

    def name(self):
        return 'Shadow Bolt'
   
    def front_row(self):
        return False

    def delay(self):
        return 1.5
    
    def take_coverable(self):
        return False

    def min_damage(self):
        return 6

    def max_damage(self):
        return 16

    def duration(self):
        return randint(1,2)

    def make_stuff_happen(self,target):
        d.sound_queue.append(S_BAD_SPELL)
        damage = randint(self.min_damage(),self.max_damage())
        target.take_damage(damage)
        if target.alive and not target.about_to_die:
            target.status_effects[EF_STUN] = Effect_parameters(self.duration())
            target.action_in_progress = None
            target.current_stance = None

    def get_description(self):
        return 'Zaps one of your back row characters, from the enemy front row. Armour is no protection. Can be countered with the "Reflection" spell.'
        
class Spell_Paralysis_MA(Monster_Spell_MA):
    def __init__(self):
        return Monster_action.__init__(self)

    def name(self):
        return 'Paralysis'

    def target_enemy(self):
        return True

    def paralysing(self):
        return True
    
    def prevented(self):
        for c in p.chars:
            if c.alive and not c.about_to_die and c.current_stance is not None and c.current_stance==ST_COUNTER_PARALYSIS:
                c.current_stance = None
                return True
            if c.alive and not c.about_to_die and c.current_stance is not None and c.current_stance==ST_COUNTER_MANY:
                c.current_stance = None
                if randint(1,100) <= min(70,success_chance(12-self.m.level)):
                    return True
                else:
                    d.message_queue.append(('Distraction failed',c.image,COL_LIGHT))
        return False

    def duration(self):
        return randint(12,20)

    def make_stuff_happen(self,target):
        d.sound_queue.append(S_BAD_SPELL)
        if target.alive and not target.about_to_die:
            immune = False
            for a in target.actions:
                if a.action_code==CA_STEELY_GAZE:
                    immune = True
                    continue
            if target.has_status_effect(EF_MEDITATION):
                immune = True
            if not immune:
                target.status_effects[EF_PARALYSIS] = Effect_parameters(self.duration())
                target.action_in_progress = None
                target.current_stance = None

    def get_description(self):
        return 'Paralyses one of your characters. Can be countered with the "Counter: Paralysis" spell.'

class Spell_Scare_MA(Monster_Spell_MA):
    def __init__(self):
        return Monster_action.__init__(self)

    def name(self):
        return 'Cause Fear'

    def target_enemy(self):
        return True
    
    def duration(self):
        return randint(8,12)

    def scary(self):
        return True

    def prevented(self):
        for c in p.chars:
            if c.alive and not c.about_to_die and c.current_stance is not None and c.current_stance==ST_COUNTER_FEAR:
                c.current_stance = None
                return True
            if c.alive and not c.about_to_die and c.current_stance is not None and c.current_stance==ST_COUNTER_MANY:
                c.current_stance = None
                if randint(1,100) <= min(70,success_chance(12-self.m.level)):
                    return True
                else:
                    d.message_queue.append(('Distraction failed',c.image,COL_LIGHT))
        return False

    def make_stuff_happen(self,target):
        d.sound_queue.append(S_BAD_SPELL)
        if target.alive and not target.about_to_die:
            immune = False
            for a in target.actions:
                if a.action_code==CA_RESIST_FEAR:
                    immune = True
                    continue
            if target.has_status_effect(EF_MEDITATION):
                immune = True
            if not immune:
                target.status_effects[EF_FEAR] = Effect_parameters(self.duration())
                target.action_in_progress = None
                target.current_stance = None

    def get_description(self):
        return 'Makes one of your characters afraid. Can be countered by the "Counter: Fear" spell.'

class Spell_Silence_MA(Monster_Spell_MA):
    def __init__(self):
        return Monster_action.__init__(self)

    def name(self):
        return 'Silence'

    def silencing(self):
        return True

    def make_stuff_happen(self,target):
       w.silence_until = w.elapsed_since_beginning + randint(6,12)

    def prevented(self):
        for c in p.chars:
            if c.alive and not c.about_to_die and c.current_stance is not None and c.current_stance==ST_COUNTER_SILENCE:
                c.current_stance = None
                return True
            if c.alive and not c.about_to_die and c.current_stance is not None and c.current_stance==ST_COUNTER_MANY:
                c.current_stance = None
                if randint(1,100) <= min(70,success_chance(12-self.m.level)):
                    return True
                else:
                    d.message_queue.append(('Distraction failed',c.image,COL_LIGHT))
        return False

    def get_description(self):
        return 'Creates a magical silence, preventing your characters from casting any spells. Can be countered by the "Counter: Silence" spell.'

class Spell_Sleep_MA(Monster_Spell_MA):
    def __init__(self):
        return Monster_action.__init__(self)

    def name(self):
        return 'Sleep'

    def sleepy(self):
        return True

    def target_enemy(self):
        return True
    
    def duration(self):
        return randint(10,15)

    def prevented(self):
        for c in p.chars:
            if c.alive and not c.about_to_die and c.current_stance is not None and c.current_stance==ST_COUNTER_SLEEP:
                c.current_stance = None
                return True
            if c.alive and not c.about_to_die and c.current_stance is not None and c.current_stance==ST_COUNTER_MANY:
                c.current_stance = None
                if randint(1,100) <= min(70,success_chance(12-self.m.level)):
                    return True
                else:
                    d.message_queue.append(('Distraction failed',c.image,COL_LIGHT))
        return False

    def make_stuff_happen(self,target):
        d.sound_queue.append(S_BAD_SPELL)
        if target.alive and not target.about_to_die:
            if target.has_status_effect(EF_MEDITATION):
                return
            target.status_effects[EF_SLEEP] = Effect_parameters(self.duration())
            target.status_effects[EF_SLEEP].level = 3
            target.action_in_progress = None
            target.current_stance = None

    def get_description(self):
        return 'Makes one of your characters fall asleep. Click on them three times to wake them up. Can be countered by the "Counter: Sleep" spell.'

class Get_Confused_MA(Monster_action):
    def __init__(self):
        return Monster_action.__init__(self)

    def name(self):
        return 'Gets confused'

    def period(self):
        return 12.5

    def cooldown(self):
        return 6.0

    def ok(self):
        if (not self.m.confused):
            return Monster_action.ok(self)
        return False

    def duration(self):
        return randint(4,6)
    
    def make_stuff_happen(self,target):
        self.m.confused = True
        self.m.confused_until = w.elapsed_since_beginning + self.duration()

    def get_description(self):
        return 'Wanders around aimlessly for a few seconds, and may hit other monsters nearby. A monster with this ability is particularly vulnerable to your confusion spells.'

class Rally_Orcs_MA(Monster_action):
    def __init__(self):
        return Monster_action.__init__(self)

    def name(self):
        return 'Rally Orcs'

    def period(self):
        return 5.0

    def cooldown(self):
        return 7.0

    def is_eligible(self,m):
        return (m.code==MOB_ORC_WARRIOR or m.code==MOB_ORC_WRETCH or m.code==MOB_ORC_ROCK_THROWER or m.code==MOB_ORC_WITH_MANCATCHER or m.code==MOB_ORC_CHAMPION)        

    def ok(self):
        for m in e.mobs:
            if m.alive and not m.about_to_die and not m==self.m:
                if self.is_eligible(m) and m.rallied_until <= w.elapsed_since_beginning:
                    return Monster_action.ok(self)
        return False

    def duration(self):
        return randint(6,12)
    
    def make_stuff_happen(self,target):
        n_rallied = 0
        for m in e.mobs:
            if m.alive and not m.about_to_die and self.is_eligible(m):
                n_rallied = n_rallied + 1
                m.rallied_until = w.elapsed_since_beginning + self.duration()
        if n_rallied>0:
            d.sound_queue.append(S_BAD_SPELL)

    def get_description(self):
        return 'Gives all the other orcs a temporary boost.'

class Burns_MA(Rally_Orcs_MA):
    def __init__(self):
        return Monster_action.__init__(self)

    def name(self):
        return ''

    def is_eligible(self,m):
        return (m.code==MOB_DARK_DWARF or m.code==MOB_DARK_DWARF_LAIRD)        
  
    def make_stuff_happen(self,target):
        message = choice(self.messages())
        d.message_queue.append(('"'+message+'"',self.m.image,COL_LIGHT))
        for m in e.mobs:
            if m.alive and not m.about_to_die and self.is_eligible(m) and m<>self.m:
                m.rallied_until = w.elapsed_since_beginning + self.duration()

    def messages(self):
        return ["A dwarf's a dwarf for a' that","The best laid schemes o' mice and men gang aft agley","Oh wad some power the giftie gie us!","Some hae meat and canna eat...","O, what a panic's in thy breastie!","Gie me ae spark o' Nature's fire!","I maun crush thy slender stem!","Welcome to your gory bed!","Nae dwarf can tether time or tide...","In hell they'll roast you like a herrin'!"]

    def get_description(self):
        return 'He can recite the works of his favorite poet, inspiring all the other dwarves to feats of arms.'

class Rally_Troglodytes_MA(Rally_Orcs_MA):
    def __init__(self):
        return Monster_action.__init__(self)

    def name(self):
        return 'Rally Troglodytes'

    def is_eligible(self,m):
        return (m.code==MOB_TROGLODYTE or m.code==MOB_TROGLODYTE_ARCHER or m.code==MOB_TROGLODYTE_SHAMAN or m.code==MOB_TROGLODYTE_PRIEST)        

    def get_description(self):
        return 'Gives all the other troglodytes a temporary boost.'

class Super_Smite_MA(Smite_MA):
    def __init__(self):
        return Monster_action.__init__(self)
    
    def name(self):
        return 'Super Smite'

    def warning_message(self):
        return '"SMASH YOU!"'

    def delay(self):
        return 2.0
 
    def period(self):
        return 8.0

    def blockable(self):
        return False # don't try to block by reflex

    def epic_blockable(self):
        return True

    def leap_awayable(self):
        return True

    def prevented(self):
        for c in p.chars:
            if c.alive and not c.about_to_die and c.current_stance is not None and c.current_stance==ST_BLOCK:
                c.current_stance = None
                if one_in(2):
                    return True
                else:
                    d.message_queue.append(('Failed to block',c.image,COL_LIGHT))
                    return False
            if c.alive and not c.about_to_die and c.current_stance is not None and c.current_stance==ST_LEAP_ASIDE:
                c.current_stance = None
                return True
            if c.alive and not c.about_to_die and c.current_stance is not None and c.current_stance==ST_COUNTER_MANY:
                c.current_stance = None
                if randint(1,100) <= min(70,success_chance(12-self.m.level)):
                    return True
                else:
                    d.message_queue.append(('Distraction failed',c.image,COL_LIGHT))
        return False

    def min_damage(self):
        return 20

    def max_damage(self):
        return 40

    def make_stuff_happen(self,target):
        d.sound_queue.append(S_SMITE)
        damage = randint(self.min_damage(),self.max_damage())
        for c in p.chars:
            if c.alive and not c.about_to_die and c.current_stance is not None and c.current_stance==ST_EPIC_BLOCK:
                target = c
                damage = max(1,damage/5)
                break
        damage_taken = max(0,damage-target.current_armor())
        target.take_damage(damage_taken)

    def get_description(self):
        return 'When it threatens to smash you, you had better defend yourself. An ordinary Block may or may not work. An Epic Block will deflect most of the damage, or Leap Aside will protect you if you time it right.'

class Cleave_MA(Monster_action):
    def __init__(self):
        return Monster_action.__init__(self)
    
    def name(self):
        return 'Cleave!'

    def warning_message(self):
        return 'Hefts his axe'

    def delay(self):
        return 2.0
 
    def period(self):
        return 8.0

    def gc(self):
        return self.delay()+1.0

    def back_row_ok(self):
        return False

    def epic_blockable(self):
        return True

    def leap_awayable(self):
        return True

    def prevented(self):
        for c in p.chars:
            if c.alive and not c.about_to_die and c.current_stance is not None and c.current_stance==ST_EPIC_BLOCK:
                return True
            if c.alive and not c.about_to_die and c.current_stance is not None and c.current_stance==ST_COUNTER_MANY:
                c.current_stance = None
                if randint(1,100) <= min(70,success_chance(12-self.m.level)):
                    return True
                else:
                    d.message_queue.append(('Distraction failed',c.image,COL_LIGHT))
        return False
    
    def min_damage(self):
        return 10

    def max_damage(self):
        return 20

    def make_stuff_happen(self,target):
        smitten = 0
        for c in p.chars:
            if c.alive and not c.about_to_die and c.front_row():
                if c.current_stance is not None and (c.current_stance==ST_BLOCK or c.current_stance==ST_LEAP_ASIDE):
                    c.current_stance is None
                else:
                    if self.m.melee_hit_roll(c):
                        damage = randint(self.min_damage(),self.max_damage())
                        damage_taken = max(0,damage-c.current_armor())
                        if damage_taken > 0:
                            smitten = smitten + 1
                            c.take_damage(damage_taken)
        if smitten>0:
            d.sound_queue.append(S_SMITE)
        else:
            d.sound_queue.append(S_MONMISS)

    def get_description(self):
        return 'When it hefts its axe, you had better defend yourself. It will attack all three front-row characters. An Epic Block will save you: an ordinary Block or Leap Aside will save only one character.'

class Charge_MA(Super_Smite_MA):
    def __init__(self):
        return Monster_action.__init__(self)
    
    def name(self):
        return 'Charge'

    def warning_message(self):
        return 'Lowers its horns'

    def delay(self):
        return 1.2
 
    def min_damage(self):
        return 12

    def max_damage(self):
        return 24

    def make_stuff_happen(self,target):
        d.sound_queue.append(S_SMITE)
        damage = randint(self.min_damage(),self.max_damage())
        for c in p.chars:
            if c.alive and not c.about_to_die and c.current_stance is not None and c.current_stance==ST_EPIC_BLOCK:
                target = c
                damage = max(1,damage/5)
                break
        damage_taken = max(0,damage-target.current_armor())
        target.take_damage(damage_taken)
        if target.alive and not target.about_to_die and damage_taken >= 5:
            target.status_effects[EF_STUN] = Effect_parameters(min(damage_taken,randint(3,6)))

    def get_description(self):
        return 'When it lowers its horns, you had better defend yourself. An ordinary Block may or may not work. An Epic Block will deflect most of the damage, or Leap Aside will protect you if you time it right.'

class Steal_MA(Monster_action):
    def __init__(self):
        return Monster_action.__init__(self)

    def period(self):
        return 1.5

    def cooldown(self):
        return 5.0

    def back_row_ok(self):
        return False

    def ok(self):
        if not Monster_action.ok(self):
            return False
        if w.collected_gold<=0:
            return False
        for c in p.chars:
            if c.alive and not c.about_to_die and c.front_row() and (c.has_status_effect(EF_STUN) or c.has_status_effect(EF_SLEEP) or c.has_status_effect(EF_PARALYSIS) or c.has_status_effect(EF_GRAPPLE)):
                return True
        return False
    
    def make_stuff_happen(self,target):
        if w.collected_gold<=0:
            return False
        for c in p.chars:
            if c.alive and not c.about_to_die and c.front_row() and (c.has_status_effect(EF_STUN) or c.has_status_effect(EF_SLEEP) or c.has_status_effect(EF_PARALYSIS) or c.has_status_effect(EF_GRAPPLE)):
                d.sound_queue.append(S_BAD_SPELL)
                gold_stolen = max(0,min(w.collected_gold,max(randint(10,40),(randint(int(floor(0.1*w.collected_gold)),int(floor(0.3*w.collected_gold)))))))
                d.log('Stolen money! - ' + str(gold_stolen))
                w.collected_gold = w.collected_gold - gold_stolen
                self.m.stolen_gold = self.m.stolen_gold + gold_stolen
                d.message_queue.append(('Stolen ' + str(gold_stolen) + ' gold from ' + c.name + '!',self.m.image,COL_LIGHT))
                return
        

    def get_description(self):
        return 'He can steal your money if you are distracted.'

class Spell_Poison_Cloud_MA(Monster_Spell_MA):
    def __init__(self):
        return Monster_action.__init__(self)

    def name(self):
        return 'Poison Cloud'

    def cooldown(self):
        return 25.0

    def make_stuff_happen(self,target):
        d.sound_queue.append(S_GAS)
        w.poison_cloud_until = w.elapsed_since_beginning + 12.0
        w.poison_cloud_from = self.m

    def get_description(self):
        return 'Creates a poisonous cloud enveloping your entire front row, which will last for some time. You had better not stand in it if you can help it, especially if you don\'t have poison resistance. If you kill the caster the poison will dissipate harmlessly.'

class Spell_Invulnerability_MA(Monster_Spell_MA):
    def __init__(self):
        return Monster_action.__init__(self)

    def name(self):
        return 'Invulnerability'

    def cooldown(self):
        return 12.0

    def make_stuff_happen(self,target):
        d.sound_queue.append(S_BAD_SPELL)
        self.m.completely_invulnerable_until = w.elapsed_since_beginning + 8.0

    def get_description(self):
        return 'Makes the caster completely invulnerable to all physical damage for a few seconds.'
   
class Mob:
    def __init__(self,code,name,description,prefer_back,max_health,level,never_encountered_randomly,evil,dragon,undead,poison_resistant,fire_resistant,stun_resistant,confusion_resistant,lightning_resistant,poison_melee,fire_melee,AP_melee,split,regenerate,min_gold,max_gold,construct,no_fly,perceptive,armor,evasion,action_codes,melee_period,melee_min_dam,melee_max_dam,melee_accuracy,melee_hit_sound,melee_miss_sound,missile_period,missile_min_dam,missile_max_dam,missile_aim,missile_hit_sound,missile_miss_sound,image,bigtile,cold_resistant):
        # parameters
        self.code = code
        self.image = image
        self.bigtile = bigtile
        self.name = name
        self.description = description
        self.prefer_back = prefer_back
        self.max_health = max_health
        self.level = level
        self.never_encountered_randomly = never_encountered_randomly
        self.evil = evil
        self.dragon = dragon
        self.undead = undead
        self.construct = construct
        self.poison_resistant = poison_resistant
        self.fire_resistant = fire_resistant
        self.cold_resistant = cold_resistant
        self.stun_resistant = stun_resistant
        self.confusion_resistant = confusion_resistant
        self.lightning_resistant = lightning_resistant
        self.poison_melee = poison_melee
        self.fire_melee = fire_melee
        self.AP_melee = AP_melee
        self.split = split
        self.regenerate = regenerate
        self.min_gold = min_gold
        self.max_gold = max_gold
        self.no_fly = no_fly
        self.perceptive = perceptive
        self.actions = []
        for action_code in action_codes:
            self.add_action(action_code)
        self.armor = armor
        self.evasion = evasion
        if melee_period>0:
            self.melee_attack = True
            self.melee_period = melee_period 
            self.melee_min_dam = melee_min_dam
            self.melee_max_dam = melee_max_dam
            self.accuracy = melee_accuracy
            self.melee_hit_sound = melee_hit_sound
            self.melee_miss_sound = melee_miss_sound
        else:
            self.melee_attack = False
        if missile_period>0:
            self.missile_attack = True
            self.missile_period = missile_period 
            self.missile_min_dam = missile_min_dam
            self.missile_max_dam = missile_max_dam
            self.aim = missile_aim
            self.missile_hit_sound = missile_hit_sound
            self.missile_miss_sound = missile_miss_sound
        else:
            self.missile_attack = False
        # and other variables 
        self.id = None # we will set this later
        self.pos = None # we will set this later
        self.alive = True
        self.about_to_die = False
        self.health = max_health
        self.gc_until = None
        self.move_to_front_at = None
        self.move_to_back_at = None
        self.flash_image = None
        self.flash_starts = None
        self.flash_number = None
        self.number_starts = None
        self.number_color = None
        self.on_fire_until = None
        self.fire_period = None
        self.poisoned_until = None
        self.poison_period = None
        self.action_in_progress = None
        self.arrived = w.elapsed_since_beginning
        self.summoner = None
        self.from_summoning_trap = False
        self.enrage = 0
        self.invulnerable_until = 0
        self.completely_invulnerable_until = 0
        self.ranged_only_evasion = 0
        self.has_been_shot = False
        self.rallied_until = 0
        self.confused = False
        self.confused_until = 0
        self.stunned = False
        self.stunned_until = 0
        self.dupe_until = 0
        self.vendetta = False
        self.slide_from = None
        self.slide_to = None
        self.slide_start = 0
        self.slide_finish = 0
        self.fade_in = False
        self.fade_start = 0
        self.fade_finish = 0
        self.xp_multiplier = 100
        self.stolen_gold = 0

    def add_action(self,action_code):
        if action_code==MA_ANTICASTER:
            action = Anticaster_MA()  
        elif action_code==MA_AURA_COOL:
            action = Aura_Cool_MA()
        elif action_code==MA_AURA_COLD:
            action = Aura_Cold_MA()
        elif action_code==MA_AURA_FIRE:
            action = Aura_Fire_MA()
        elif action_code==MA_AURA_FIRE_RAGE:
            action = Aura_Fire_Rage_MA()
        elif action_code==MA_BITE:
            action = Bite_MA()   ## DO LATER
        elif action_code==MA_BLOCK:
            action = Block_MA()  
        elif action_code==MA_BREATH_FIRE:
            action = Breath_Fire_MA()
        elif action_code==MA_BREATH_LIGHTNING:
            action = Breath_Lightning_MA()  ## DO LATER
        elif action_code==MA_BREATH_POISON_CLOUD:
            action = Breath_Poison_Cloud_MA()   ## DO LATER
        elif action_code==MA_CATCH:
            action = Catch_MA()   
        elif action_code==MA_CLAW:
            action = Claw_MA()   
        elif action_code==MA_CLAW_FAST:
            action = Claw_Fast_MA()   
        elif action_code==MA_DARKNESS:
            action = Darkness_MA()
        elif action_code==MA_DWARF_SPIRITS:
            action = Dwarf_Spirits_MA()   
        elif action_code==MA_ENTER_SHELL:
            action = Enter_Shell_MA()  
        elif action_code==MA_EXIT_SHELL:
            action = Exit_Shell_MA()   
        elif action_code==MA_EXPLODE:
            action = Explode_MA()    
        elif action_code==MA_FINISHING_BLOW:
            action = Finishing_Blow_MA()   
        elif action_code==MA_FIREBOLT:
            action = Firebolt_MA()   
        elif action_code==MA_FLEE:
            action = Flee_MA()     
        elif action_code==MA_FLEE_FAST:
            action = Flee_Fast_MA()  
        elif action_code==MA_FLEE_SLOWLY:
            action = Flee_Slowly_MA()  
        elif action_code==MA_FLIT:
            action = Flit_MA()    
        elif action_code==MA_FLIT_TAUNT:
            action = Flit_Taunt_MA()  
        elif action_code==MA_FLY:
            action = Fly_MA()
        elif action_code==MA_GAZE_DRAIN_BAR:
            action = Gaze_Drain_Bar_MA()
        elif action_code==MA_GAZE_PARALYSIS:
            action = Gaze_Paralysis_MA()   
        elif action_code==MA_GAZE_PARALYSIS_OFTEN:
            action = Gaze_Paralysis_Often_MA()  
        elif action_code==MA_GAZE_SCARE:
            action = Gaze_Scare_MA()  
        elif action_code==MA_GAZE_SLEEP:
            action = Gaze_Sleep_MA()  
        elif action_code==MA_GET_CONFUSED:
            action = Get_Confused_MA()  
        elif action_code==MA_GRAPPLE:
            action = Grapple_MA()  
        elif action_code==MA_HOWL:
            action = Howl_MA()  
        elif action_code==MA_IRISH:
            action = Irish_MA()
        elif action_code==MA_ARGOT:
            action = Argot_MA()
        elif action_code==MA_LEAP_AWAY:
            action = Leap_Away_MA()  ## DO LATER
        elif action_code==MA_METEOR_SWARM:
            action = Meteor_Swarm_MA()   ## DO LATER
        elif action_code==MA_RALLY_ORCS:
            action = Rally_Orcs_MA()  
        elif action_code==MA_RALLY_TROGLODYTES:
            action = Rally_Troglodytes_MA() 
        elif action_code==MA_SCOTS:
            action = Scots_MA()   
        elif action_code==MA_SHRIEK_SCARE:
            action = Shriek_Scare_MA()  
        elif action_code==MA_SILENCE:
            action = Silence_MA()   
        elif action_code==MA_SMITE:
            action = Smite_MA()
        elif action_code==MA_SMITE_BLACK_KNIGHT:
            action = Smite_Black_Knight_MA()
        elif action_code==MA_SMITE_BLACK_OGRE:
            action = Smite_Black_Ogre_MA()
        elif action_code==MA_SMITE_DARK_ELF:
            action = Smite_Dark_Elf_MA()
        elif action_code==MA_SMITE_DARK_PRIEST:
            action = Smite_Dark_Priest_MA()
        elif action_code==MA_SMITE_NAGA:
            action = Smite_Naga_MA()
        elif action_code==MA_SMITE_OGRE:
            action = Smite_Ogre_MA()
        elif action_code==MA_SMITE_ORC_BOSS:
            action = Smite_Orc_Boss_MA()
        elif action_code==MA_SMITE_ORC_CHAMPION:
            action = Smite_Orc_Champion_MA()
        elif action_code==MA_SNIPE:
            action = Snipe_MA()
        elif action_code==MA_SNIPE_MANTICORE:
            action = Snipe_Manticore_MA()
        elif action_code==MA_SNIPE_DARK_ELF:
            action = Snipe_Dark_Elf_MA()
        elif action_code==MA_SNIPE_NAGA:
            action = Snipe_Naga_MA()
        elif action_code==MA_SNIPE_ORC_BOSS:
            action = Snipe_Orc_Boss_MA()
        elif action_code==MA_SOFT_ENRAGE:
            action = Soft_Enrage_MA()  
        elif action_code==MA_SPELL_CAUSE_WOUNDS:
            action = Spell_Cause_Wounds_MA()  
        elif action_code==MA_SPELL_DEFLECT_MISSILES:
            action = Spell_Deflect_Missiles_MA()  
        elif action_code==MA_SPELL_FIREBOLT:
            action = Spell_Firebolt_MA()  
        elif action_code==MA_SPELL_HEAL:
            action = Spell_Heal_MA()  
        elif action_code==MA_SPELL_INVULNERABILITY:
            action = Spell_Invulnerability_MA()  
        elif action_code==MA_SPELL_LIGHTNING:
            action = Spell_Lightning_MA()  
        elif action_code==MA_SPELL_PARALYSIS:
            action = Spell_Paralysis_MA()  
        elif action_code==MA_SPELL_POISON_CLOUD:
            action = Spell_Poison_Cloud_MA()  
        elif action_code==MA_SPELL_SCARE:
            action = Spell_Scare_MA()  
        elif action_code==MA_SPELL_SILENCE:
            action = Spell_Silence_MA()  
        elif action_code==MA_SPELL_SLEEP:
            action = Spell_Sleep_MA()  
        elif action_code==MA_SPIT:
            action = Spit_MA()
        elif action_code==MA_SQUEEZE:
            action = Squeeze_MA()  
        elif action_code==MA_SQUEEZE_NASTY:
            action = Squeeze_Nasty_MA()  ## DO LATER
        elif action_code==MA_STUN:
            action = Stun_MA()    
        elif action_code==MA_SUMMON_ANIMALS:
            action = Summon_Animals_MA()  
        elif action_code==MA_SUMMON_COBRAS:
            action = Summon_Cobras_MA()  
        elif action_code==MA_SUMMON_EVIL_TRASH:
            action = Summon_Evil_Trash_MA()  
        elif action_code==MA_SUMMON_GOLEM:
            action = Summon_Golem_MA()  
        elif action_code==MA_SUMMON_ORCS:
            action = Summon_Orcs_MA()  
        elif action_code==MA_SUMMON_REPTILES:
            action = Summon_Reptiles_MA()  
        elif action_code==MA_SUMMON_SNAKES:
            action = Summon_Snakes_MA()
        elif action_code==MA_SUMMON_TROGLODYTES:
            action = Summon_Troglodytes_MA()  
        elif action_code==MA_SUMMON_UNDEAD:
            action = Summon_Undead_MA()  
        elif action_code==MA_SUMMON_VARIOUS:
            action = Summon_Various_MA()  
        elif action_code==MA_SUMMON_BODYGUARD:
            action = Summon_Bodyguard_MA()  
        elif action_code==MA_SUMMON_WRETCH:
            action = Summon_Wretch_MA()  
        elif action_code==MA_SUPER_SMITE:
            action = Super_Smite_MA()  
        elif action_code==MA_SWOOP:
            action = Swoop_MA()   
        elif action_code==MA_TAILSWIPE:
            action = TailSwipe_MA()  
        elif action_code==MA_ZAP:
            action = Zap_MA()  
        elif action_code==MA_HURL_AND_ADVANCE:
            action = Hurl_And_Advance_MA()  
        elif action_code==MA_ADVANCE:
            action = Advance_MA()
        elif action_code==MA_CHILL_DART:
            action = Spell_Chill_Dart_MA()
        elif action_code==MA_SHOVE:
            action = Shove_MA()
        elif action_code==MA_SHADOW_BOLT:
            action = Spell_Shadow_Bolt_MA()
        elif action_code==MA_SUMMON_FRONT:
            action = Spell_Summon_Front_MA()
        elif action_code==MA_DROP_BOULDER:
            action = Drop_Boulder_MA()
        elif action_code==MA_CHARGE:
            action = Charge_MA()
        elif action_code==MA_STEAL:
            action = Steal_MA()
        elif action_code==MA_SUMMON_LOW_UNDEAD:
            action = Summon_Low_Undead_MA()
        elif action_code==MA_STING_PARALYSIS:
            action = Sting_Paralysis_MA()
        elif action_code==MA_BREATH_SMALL_FIRE:
            action = Breath_Small_Fire_MA()
        elif action_code==MA_BURNS:
            action = Burns_MA()
        elif action_code==MA_CURSE:
            action = Curse_MA()
        elif action_code==MA_TORNADO:
            action = Tornado_MA()
        elif action_code==MA_SNIPE_POLTERGEIST:
            action = Snipe_Poltergeist_MA()
        elif action_code==MA_CLEAVE:
            action = Cleave_MA()
        elif action_code==MA_SMITE_BERSERKER:
            action = Smite_Berserker_MA()
        else:
            return
        action.m = self
        action.action_code = action_code
        self.actions.append(action)

    def seconds_since_arrived(self):
        return w.elapsed_since_beginning - self.arrived        

    def get_chardump(self):
        dump = []
        dump.append(self.name.upper())
        dump.append(self.description)
        dump.append('--Status--')
        dump.append('Health ' + str(self.health) + ' / ' + str(self.max_health))
        if self.rallied_until > w.elapsed_since_beginning:
            dump.append('This monster has been rallied for a temporary boost')
        if self.confused:
            dump.append('This monster seems confused')
        if self.stunned:
            dump.append('This monster is stunned')
        if self.vendetta:
            dump.append('Your party hates this monster')
        if self.evil:
            dump.append('This monster is evil')
        dump.append('--Defenses--')
        dump.append('Armor   ' + str(self.current_armor()))
        dump.append('Evasion ' + str(self.evasion))
        if self.poison_resistant:
            dump.append('Poison resistant')
        if self.fire_resistant:
            dump.append('Fire resistant')
        if self.cold_resistant:
            dump.append('Cold resistant')
        if self.lightning_resistant:
            dump.append('Lightning resistant')
        if self.stun_resistant:
            dump.append('Stun resistant')
        if self.confusion_resistant:
            dump.append('Confusion resistant')
        if self.ranged_only_evasion > 0:
            dump.append('Evasion increased by ' + str(self.ranged_only_evasion) + ' at long range')
        if self.melee_attack:
            dump.append('--Base melee attack--')
            if self.poison_melee==1:
                dump.append('Poisonous')
            if self.poison_melee>=2:
                dump.append('Very poisonous!')
            if self.fire_melee:
                dump.append('Damage halved if you are fire resistant')
            if self.AP_melee:
                dump.append('Ignores armor')
            dump.append('Accuracy ' + str(self.accuracy))
            dump.append('Rate of 1 per ' + str(round_to_1_dp(self.melee_period/TIME_SCALAR)) + ' s')
            if self.enrage:
                dump.append('Damage ' + str(self.melee_min_dam) + '-' + str(self.melee_max_dam) + ', +' + str((pow(self.enrage,2))*10) + '% for enrage')
            else:
                dump.append('Damage ' + str(self.melee_min_dam) + '-' + str(self.melee_max_dam))
        if self.missile_attack:
            dump.append('--Base missile attack--')
            dump.append('Aim ' + str(self.aim))
            dump.append('Rate of 1 per ' + str(round_to_1_dp(self.missile_period/TIME_SCALAR)) + ' s')
            dump.append('Damage ' + str(self.missile_min_dam) + '-' + str(self.missile_max_dam))
        if len(self.actions)>0 or self.split or self.regenerate:
            dump.append('--Abilities--')
            if self.split:
                dump.append('It can split in half!')
            if self.regenerate:
                dump.append('It regenerates its health bar.')
            if len(self.actions)>0:
                for action in self.actions:
                    desc = action.get_description()
                    if desc<>'':
                        dump.append("  " + action.name() + ": " + desc)
        return dump

    def get_container_chardump(self):
        text_style = {'font': pygame.font.Font("files/FreeSans.ttf",     20), 'padding_right': 10}
        bold_style = {'font': pygame.font.Font("files/FreeSansBold.ttf", 20), 'padding_right': 10}
        italic_style = {'font': pygame.font.Font("files/FreeSansOblique.ttf", 20), 'padding_right': 10}
        dump = gui.Table()
        dump.tr()
        dump.td(gui.Spacer(height=20,width=280),colspan=2)
        dump.tr()
        for line in textwrap.wrap(self.description,32):
            dump.td(gui.Label(line, style=text_style), align=-1,colspan=2)
            dump.tr()
        dump.td(gui.Spacer(height=20,width=280),colspan=2)
        dump.tr()
        dump.td(gui.Label("Status", style=bold_style), align=-1,colspan=2)
        dump.tr()
        dump.td(gui.Label("Health:", style=text_style), align=-1)
        dump.td(gui.Label(str(self.health) + ' / ' + str(self.max_health), style=text_style), align=-1)
        dump.tr()
        if self.rallied_until > w.elapsed_since_beginning:
            dump.td(gui.Label('This monster has been rallied', style=italic_style), align=-1, colspan=2)
            dump.tr()
            dump.td(gui.Label('  for a temporary boost', style=italic_style), align=-1, colspan=2)
            dump.tr()
        if self.confused:
            dump.td(gui.Label('This monster seems confused', style=italic_style), align=-1, colspan=2)
            dump.tr()
        if self.stunned:
            dump.td(gui.Label('This monster is stunned', style=italic_style), align=-1, colspan=2)
            dump.tr()
        if self.vendetta:
            dump.td(gui.Label('Your party hates this monster', style=italic_style), align=-1, colspan=2)
            dump.tr()
        if self.evil:
            dump.td(gui.Label('This monster is evil', style=text_style), align=-1, colspan=2)
            dump.tr()
        dump.td(gui.Spacer(height=20,width=280),colspan=2)
        dump.tr()
        dump.td(gui.Label("Defenses", style=bold_style), align=-1,colspan=2)
        dump.tr()
        dump.td(gui.Label("Armor", style=text_style), align=-1)
        dump.td(gui.Label(str(self.current_armor()), style=text_style), align=-1)
        dump.tr()
        dump.td(gui.Label("Evasion", style=text_style), align=-1)
        dump.td(gui.Label(str(self.evasion), style=text_style), align=-1)
        dump.tr()
        if self.poison_resistant:
            dump.td(gui.Label('Poison resistant', style=text_style), align=-1, colspan=2)
            dump.tr()
        if self.fire_resistant:
            dump.td(gui.Label('Fire resistant', style=text_style), align=-1, colspan=2)
            dump.tr()
        if self.cold_resistant:
            dump.td(gui.Label('Cold resistant', style=text_style), align=-1, colspan=2)
            dump.tr()
        if self.lightning_resistant:
            dump.td(gui.Label('Lightning resistant', style=text_style), align=-1, colspan=2)
            dump.tr()
        if self.stun_resistant:
            dump.td(gui.Label('Stun resistant', style=text_style), align=-1, colspan=2)
            dump.tr()
        if self.confusion_resistant:
            dump.td(gui.Label('Confusion resistant', style=text_style), align=-1, colspan=2)
            dump.tr()
        if self.ranged_only_evasion > 0:
            dump.td(gui.Label('Protected from missiles', style=italic_style), align=-1, colspan=2)
            dump.tr()
        if self.melee_attack:
            dump.td(gui.Spacer(height=20,width=280),colspan=2)
            dump.tr()
            dump.td(gui.Label("Base melee attack", style=bold_style), align=-1,colspan=2)
            dump.tr()
            if self.poison_melee==1:
                dump.td(gui.Label('Poisonous', style=italic_style), align=-1, colspan=2)
                dump.tr()
            if self.poison_melee>=2:
                dump.td(gui.Label('Very poisonous!', style=italic_style), align=-1, colspan=2)
                dump.tr()
            if self.fire_melee:
                dump.td(gui.Label('Damage halved if you', style=italic_style), align=-1, colspan=2)
                dump.tr()
                dump.td(gui.Label(' are fire resistant', style=italic_style), align=-1, colspan=2)
                dump.tr()
            if self.AP_melee:
                dump.td(gui.Label('Ignores armor', style=italic_style), align=-1, colspan=2)
                dump.tr()
            dump.td(gui.Label("Accuracy", style=text_style), align=-1)
            dump.td(gui.Label(str(self.accuracy), style=text_style), align=-1)
            dump.tr()
            dump.td(gui.Label('Rate of 1 per ' + str(round_to_1_dp(self.melee_period/TIME_SCALAR)) + ' s', style=text_style), align=-1, colspan=2)
            dump.tr()
            if self.enrage:
                dump.td(gui.Label('Damage ' + str(self.melee_min_dam) + '-' + str(self.melee_max_dam) + ', +' + str((pow(self.enrage,2))*10) + '% for enrage', style=text_style), align=-1, colspan=2)
            else:
                dump.td(gui.Label('Damage ' + str(self.melee_min_dam) + '-' + str(self.melee_max_dam), style=text_style), align=-1, colspan=2)
            dump.tr()
        if self.missile_attack:
            dump.td(gui.Spacer(height=20,width=280),colspan=2)
            dump.tr()
            dump.td(gui.Label("Base missile attack", style=bold_style), align=-1,colspan=2)
            dump.tr()
            dump.td(gui.Label("Aim", style=text_style), align=-1)
            dump.td(gui.Label(str(self.aim), style=text_style), align=-1)
            dump.tr()
            dump.td(gui.Label('Rate of 1 per ' + str(round_to_1_dp(self.missile_period/TIME_SCALAR)) + ' s', style=text_style), align=-1, colspan=2)
            dump.tr()
            dump.td(gui.Label('Damage ' + str(self.missile_min_dam) + '-' + str(self.missile_max_dam), style=text_style), align=-1, colspan=2)
            dump.tr()
        if len(self.actions)>0 or self.split or self.regenerate:
            dump.td(gui.Spacer(height=20,width=280),colspan=2)
            dump.tr()
            dump.td(gui.Label("Abilities", style=bold_style), align=-1,colspan=2)
            dump.tr()
            if self.split:
                dump.td(gui.Label('It can split in half.', style=italic_style), align=-1, colspan=2)
                dump.tr()
            if self.regenerate:
                dump.td(gui.Label('It regenerates its health.', style=italic_style), align=-1, colspan=2)
                dump.tr()
            if len(self.actions)>0:
                for action in self.actions:
                    line = action.get_description()
                    if line<>'':
                        dump.td(gui.Spacer(height=5,width=280),colspan=2)
                        dump.tr()
                        dump.td(gui.Label(action.name(), style=italic_style), align=-1,colspan=2)
                        dump.tr()
                        sublines = textwrap.wrap(line,30)
                        dump.td(gui.Label(' '+sublines[0], style=text_style), align=-1, colspan=2)
                        dump.tr()
                        if len(sublines)>1:
                            for subline in sublines[1:]:
                                dump.td(gui.Label('  '+subline, style=text_style), align=-1, colspan=2)
                                dump.tr()
        dump.td(gui.Spacer(height=20,width=280),colspan=2)
        dump.tr()
        return dump

    def melee_hit_roll(self,target):
        target = 2 * (5 + self.accuracy - target.current_evasion())
        if target<1:
            target = 1
        if target>19:
            target = 19
        if randint(1,20)<=target:
            return True
        else:
            return False

    def missile_hit_roll(self,target):
        target = 2 * (5 + self.aim - target.current_evasion())
        if target<1:
            target = 1
        if target>19:
            target = 19
        if randint(1,20)<=target:
            return True
        else:
            return False

    def melee_damage_roll(self):
        base = randint(self.melee_min_dam,self.melee_max_dam)
        if self.enrage>0:
            return int(floor((base * (100 + (pow(self.enrage,2))*10))/100))
        else:
            return base

    def missile_damage_roll(self):
        return randint(self.missile_min_dam,self.missile_max_dam)

    def current_armor(self):
        if self.invulnerable_until > w.elapsed_since_beginning:
            return 999
        else:
            return self.armor

    def current_evasion(self):
        if self.front_row():
            return self.evasion
        else:
            return self.evasion + self.ranged_only_evasion
        
    def take_damage(self,damage):
        if self.completely_invulnerable_until > w.elapsed_since_beginning:
            damage = 0
        d.log(self.name + ' takes ' + str(damage) + ' damage')
        self.health = self.health - damage
        if damage > 0:
            self.flash_number = damage
            self.number_color = [255,0,0]
            self.number_starts = w.elapsed_since_beginning
        if self.health<=0:
            self.about_to_die = True
            self.die_at = w.elapsed_since_beginning + 1.5
            d.sound_queue.append(S_MONSTER_OUCH)
            d.log(self.name + ' killed')
#            d.message_queue.append(('Killed!',self.image,COL_LIGHT))
            if self.evil:
                n_awarded = 0
                failed = False
                while (n_awarded < 3 and not failed):
                    failed = True
                    for c in p.chars:
                        if c.alive and not c.about_to_die and c.energy < c.max_energy and c.energy_for_evil_kill and n_awarded < 3:
                            c.energy = c.energy + 1
                            n_awarded = n_awarded + 1
                            failed = False
                w.evil_kills.append(w.elapsed_since_beginning)
            return
        if damage>min(5,self.max_health/10) and self.action_in_progress is not None and self.action_in_progress.disruptable():
            d.log('Action disrupted!')
            d.message_queue.append(('Action prevented!',self.image,COL_LIGHT))
            self.action_in_progress = None
            
    def die(self):
        w.waiting_gold = w.waiting_gold + randint(self.min_gold,self.max_gold) + self.stolen_gold
        xp_per_char = (p.xp_multiplier * self.xp_value() * self.xp_multiplier) / (10000 * p.n_surviving_chars())
        for c in p.chars:
            if c.alive and not c.about_to_die:
                c.xp = c.xp + xp_per_char
                d.log('Giving ' + str(xp_per_char) + ' XP to ' + c.name)
        self.remove()

    def xp_value(self):
        return [10,30,100,300,1000,3000,10000,30000,100000][self.level-1]

    def flee(self):
        d.log(self.name + ' fled')
        d.message_queue.append(('Fled',self.image,COL_LIGHT))
        self.slide_from = self.pos
        self.slide_to = POS_BACK_RIGHT
        self.remove()
        self.slide_start = w.elapsed_since_beginning
        self.slide_finish = w.elapsed_since_beginning + 0.3
        
    def remove(self):
        front_row = self.front_row()
        self.pos = None
        self.alive = False
        self.about_to_die = False
        if front_row:
            if e.n_surviving_front_mobs()==0:
                e.push_monster_forward(9999)
            else:
                e.push_monster_forward(-1)
        else:
            e.push_monster_back(-1)
        if e.n_surviving_mobs()==0:
            w.clear = True
            w.clear_as_of = w.elapsed_since_beginning
        for m in e.mobs:
            if m.construct and m.alive and not m.about_to_die and m.summoner==self:
                m.die()
        for c in p.chars:
            if c.grappled_by==self:
                c.grappled_by = None
                c.status_effects[EF_GRAPPLE].lasts_until = 0
            
    def front_row(self):
        return (self.pos==POS_M_FRONT_1 or self.pos==POS_M_FRONT_2 or self.pos==POS_M_FRONT_3)        

    def blocked_by_gc(self):
        for m in e.mobs:
            if m.gc_until is not None and m.gc_until > w.elapsed_since_beginning and m.level>=self.level:
                return True
        return False

    def can_act(self):
        if not self.alive:
            return False
        if self.about_to_die:
            return False
        if self.pos is None:
            return False
        if self.action_in_progress is not None:
            return False
        return True

class Enemies:
    def __init__(self):
        self.reset()
        
    def reset(self):
        self.mobs = []
        self.last_free_id = 0

    def get_monster_with_id(self,id):        
        for m in self.mobs:
            if m.id==id:
                return m
        return None

    def get_monster_at_pos(self,pos):
        for m in self.mobs:
            if m.pos==pos:
                return m
        return None
        
    def add_mob(self,mob_code):
        mob = self.create_mob(mob_code)
        if mob.prefer_back and e.n_surviving_front_mobs()>0:
            pos = self.last_free_back_pos()
            if pos is None:
                if self.push_monster_forward(mob.level):
                    pos = self.last_free_back_pos()
                else:
                    pos = self.last_free_front_pos()
        else:
            pos = self.last_free_front_pos()
            if pos is None:
                if self.push_monster_back(mob.level):
                    pos = self.last_free_front_pos()
                else:
                    pos = self.last_free_back_pos()
        if pos is not None:
            mob.slide_from = POS_BACK_LEFT
            mob.slide_to = pos
            mob.slide_start = w.elapsed_since_beginning
            mob.slide_finish = w.elapsed_since_beginning + 0.3
            mob.pos = pos
            mob.id = self.last_free_id
            self.mobs.append(mob)
            self.last_free_id = self.last_free_id + 1
            return self.get_monster_with_id(self.last_free_id-1)
        else:
            d.log('No room for ' + mob.name)
            return None

    def create_mob(self,code):
        if code==MOB_ORC_WRETCH:
            m = Mob(code,'ORC WRETCH','',False,15,1,False,True,False,False,False,False,False,False,False,0,False,False,False,False,0,0,False,False,False,0,1,[],2,1,5,0,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_ORC_PEON,False,False)
        elif code==MOB_KOBOLD:
            m = Mob(code,'KOBOLD','',False,20,1,False,True,False,False,False,False,False,False,False,0,False,False,False,False,1,3,False,False,False,0,2,[],4,2,7,2,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_KOBOLD,False,False)
        elif code==MOB_CENTIPEDE:
            m = Mob(code,'CENTIPEDE','',False,25,1,False,False,False,False,True,False,False,False,False,0,False,False,False,False,0,0,False,False,False,0,0,[],5,1,8,1,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_CENTIPEDE,False,False)
        elif code==MOB_ORC_ROCK_THROWER:
            m = Mob(code,'ORC ROCK THROWER','',True,10,2,False,True,False,False,False,False,False,False,False,0,False,False,False,False,2,4,False,False,False,0,2,[],4,1,5,0,S_MONHIT,S_MONMISS,3,1,6,1,S_SHOOT,S_MONMISS,I_ORC_ROCK_THROWER,False,False)
        elif code==MOB_SNAKE:
            m = Mob(code,'SNAKE','',False,10,2,False,False,False,False,True,False,False,False,False,1,False,False,False,False,0,0,False,False,False,0,0,[],2,1,6,4,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_SNAKE,False,False)
        elif code==MOB_UGLY_THING:
            m = Mob(code,'UGLY THING','',True,10,2,False,False,False,False,False,False,False,False,False,0,False,False,False,False,0,0,False,False,False,0,0,[MA_GAZE_SCARE],5,1,6,1,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_UGLY_THING,False,False)
        elif code==MOB_GIANT_ANT:
            m = Mob(code,'GIANT ANT','',False,15,2,False,False,False,False,False,False,False,False,False,0,False,False,False,False,0,0,False,False,False,1,1,[],2,1,6,2,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_GIANT_ANT,False,False)
        elif code==MOB_TROGLODYTE:
            m = Mob(code,'TROGLODYTE','',False,15,2,False,True,False,False,True,False,False,False,False,0,False,False,False,False,3,7,False,False,False,0,2,[MA_FLEE],4,1,8,1,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_BASIC_TROGLODYTE,False,False)
        elif code==MOB_HARPY:
            m = Mob(code,'HARPY','',True,25,2,False,True,False,False,False,False,False,False,False,0,False,False,False,False,0,0,False,True,False,0,5,[MA_SWOOP, MA_FLIT_TAUNT],3,1,6,2,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_HARPY,False,False)
        elif code==MOB_BLAST_SPORE:
            m = Mob(code,'BLAST SPORE','',True,1,3,False,False,False,False,True,False,False,True,False,0,False,False,False,False,0,0,False,False,False,0,0,[MA_ADVANCE, MA_EXPLODE],0,0,0,0,0,0,0,0,0,0,0,0,I_BLAST_SPORE,False,False)
        elif code==MOB_GIANT_FIREFLY:
            m = Mob(code,'GIANT FIREFLY','',True,1,3,False,False,False,False,False,True,False,False,False,0,False,False,False,False,0,0,False,True,False,0,7,[MA_ZAP, MA_FLIT],4,1,5,3,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_FIREFLY,False,False)
        elif code==MOB_GIANT_WASP:
            m = Mob(code,'GIANT WASP','',True,1,3,False,False,False,False,True,False,False,False,False,1,False,False,False,False,0,0,False,True,False,0,7,[MA_FLIT],3,1,8,2,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_WASP,False,False)
        elif code==MOB_TROGLODYTE_ARCHER:
            m = Mob(code,'TROGLODYTE ARCHER','',True,15,3,False,True,False,False,True,False,False,False,False,0,False,False,False,False,5,10,False,False,False,0,3,[MA_SNIPE],3,1,6,2,S_MONHIT,S_MONMISS,5,1,10,3,S_SHOOT,S_MONMISS,I_TROGLODYTE,False,False)
        elif code==MOB_POLTERGEIST:
            m = Mob(code,'POLTERGEIST','',True,15,3,False,False,False,True,True,True,True,False,False,0,False,False,False,False,5,15,False,False,False,0,5,[MA_SNIPE_POLTERGEIST, MA_FLIT],4,1,4,1,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_POLTERGEIST,False,True)
        elif code==MOB_COBRA:
            m = Mob(code,'COBRA','',True,20,3,False,False,False,False,True,False,False,False,False,1,False,False,False,False,0,0,False,False,False,1,3,[MA_SPIT],2,1,8,5,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_COBRA,False,False)
        elif code==MOB_SKELETON:
            m = Mob(code,'SKELETON','',False,35,3,False,True,False,True,True,False,False,True,False,0,False,False,False,False,0,0,False,False,False,1,1,[],4,3,10,4,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_SKELETON_MOB,False,True)
        elif code==MOB_GOBLIN:
            m = Mob(code,'GOBLIN','',False,40,3,False,True,False,False,False,False,False,False,False,0,False,False,False,False,5,15,False,False,False,3,3,[MA_BLOCK],3.5,3,9,3,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_GOBLIN,False,False)
        elif code==MOB_JELLY:
            m = Mob(code,'JELLY','',False,40,3,False,False,False,False,True,False,True,True,False,0,False,False,True,False,0,0,False,False,False,0,0,[],5,1,12,2,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_JELLY,False,False)
        elif code==MOB_WOLF:
            m = Mob(code,'WOLF','',False,40,3,False,False,False,False,False,False,False,False,False,0,False,False,False,False,0,0,False,False,True,0,3,[],3,3,8,3,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_WOLF,False,False)
        elif code==MOB_BIG_KOBOLD:
            m = Mob(code,'BIG KOBOLD','',False,50,3,False,True,False,False,False,False,False,False,False,0,False,False,False,False,5,15,False,False,False,2,4,[],4,3,10,2,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_BIG_KOBOLD,False,False)
        elif code==MOB_ORC_WARRIOR:
            m = Mob(code,'ORC WARRIOR','',False,50,3,False,True,False,False,False,False,False,False,False,0,False,False,False,False,5,15,False,False,False,3,1,[MA_SMITE],4,3,10,4,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_ORC,False,False)
        elif code==MOB_PYTHON:
            m = Mob(code,'PYTHON','',False,50,3,False,False,False,False,True,False,False,False,False,0,False,False,False,False,0,0,False,False,False,2,0,[MA_GRAPPLE, MA_SQUEEZE],4,3,8,3,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_PYTHON,False,False)
        elif code==MOB_MILLIPEDE:
            m = Mob(code,'MILLIPEDE','',False,65,3,False,False,False,False,True,False,False,False,False,0,False,False,False,False,0,0,False,False,False,2,2,[],2,4,10,2,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_MILLIPEDE,False,False)
        elif code==MOB_LEPRECHAUN:
            m = Mob(code,'LEPRECHAUN','',True,30,4,False,False,False,False,False,False,False,False,False,0,False,False,False,False,50,150,False,False,False,0,5,[MA_IRISH, MA_FLEE_FAST],4,1,8,2,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_LEPRECHAUN,False,False)
        elif code==MOB_ORC_AGITATOR:
            m = Mob(code,'ORC AGITATOR','',True,30,4,False,True,False,False,False,False,False,False,False,0,False,False,False,False,5,15,False,False,False,0,3,[MA_RALLY_ORCS],3,3,8,3,S_MONHIT,S_MONMISS,4,1,8,2,S_SHOOT,S_MONMISS,I_ORC_AGITATOR,True,False)
        elif code==MOB_TROGLODYTE_SHAMAN:
            m = Mob(code,'TROGLODYTE SHAMAN','',True,30,4,False,True,False,False,True,False,False,False,False,0,False,False,False,False,10,20,False,False,False,2,2,[MA_SPELL_SCARE, MA_SPELL_PARALYSIS, MA_SPELL_SLEEP],4,3,8,3,S_MONHIT,S_MONMISS,5,1,10,3,S_SHOOT,S_MONMISS,I_TROGLODYTE_SHAMAN,False,False)
        elif code==MOB_FROZEN_SPIRIT:
            m = Mob(code,'FROZEN SPIRIT','',False,40,4,False,False,False,True,True,False,True,False,False,0,False,True,False,False,10,20,False,False,True,0,5,[MA_AURA_COOL, MA_CHILL_DART],3,1,8,3,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_FROZEN_SPIRIT,False,True)
        elif code==MOB_YOUNG_NECROMANCER:
            m = Mob(code,'YOUNG NECROMANCER','',True,40,4,False,True,False,False,False,False,False,False,False,0,False,False,False,False,10,30,False,False,False,0,2,[MA_SHADOW_BOLT,MA_SUMMON_LOW_UNDEAD],5,1,8,2,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_YOUNG_NECROMANCER,False,False)
        elif code==MOB_SCORPION:
            m = Mob(code,'GIANT SCORPION','',False,50,4,False,False,False,False,True,False,False,False,False,2,False,False,False,False,0,0,False,False,False,3,3,[],3,5,10,4,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_SCORPION,False,False)
        elif code==MOB_ORC_SLAVEMASTER:
            m = Mob(code,'ORC SLAVEMASTER','',True,50,4,False,True,False,False,False,False,False,False,False,0,False,False,False,False,10,20,False,False,False,0,3,[MA_SUMMON_WRETCH, MA_FLEE_SLOWLY],2,4,10,2,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_ORC_SLAVEMASTER,True,False)
        elif code==MOB_SPECTRE:
            m = Mob(code,'SPECTRE','',True,50,4,False,True,False,True,True,False,True,False,False,0,False,True,False,False,10,20,False,True,True,0,5,[MA_GAZE_DRAIN_BAR],3,1,8,3,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_SPECTRE,False,True)
        elif code==MOB_ORC_WITH_MANCATCHER:
            m = Mob(code,'ORC WITH MANCATCHER','',False,60,4,False,True,False,False,False,False,False,False,False,0,False,False,False,False,10,20,False,False,False,3,2,[MA_CATCH, MA_FINISHING_BLOW],2,4,10,2,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_ORC_WITH_MANCATCHER,True,False)
        elif code==MOB_THUG:
            m = Mob(code,'THUG','',False,60,4,False,True,False,False,False,False,False,False,False,0,False,False,False,False,10,20,False,False,False,1,5,[MA_STUN, MA_ARGOT, MA_FLEE_FAST, MA_STEAL],3,4,10,4,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_THUG,False,False)
        elif code==MOB_BEAR:
            m = Mob(code,'ANGRY BEAR','',False,65,4,False,False,False,False,False,False,False,False,False,0,False,False,False,False,0,0,False,False,False,2,2,[MA_GRAPPLE, MA_SQUEEZE],3,5,15,2,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_BEAR,False,False)
        elif code==MOB_BEETLE:
            m = Mob(code,'GIANT BEETLE','',False,65,4,False,False,False,False,False,False,False,False,False,0,False,False,False,False,5,15,False,False,False,5,0,[],2,5,15,2,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_BEETLE,False,False)
        elif code==MOB_OOZE:
            m = Mob(code,'OOZE','',False,65,4,False,False,False,False,True,False,True,True,False,0,False,False,False,True,0,0,False,False,False,0,0,[],3,3,8,3,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_OOZE,False,False)
        elif code==MOB_TROGLODYTE_PRIEST:
            m = Mob(code,'TROGLODYTE PRIEST','',True,50,5,False,True,False,False,True,False,False,False,False,0,False,False,False,False,10,20,False,False,False,2,2,[MA_SPELL_HEAL, MA_SUMMON_SNAKES, MA_RALLY_TROGLODYTES, MA_FLEE],4,2,8,1,S_MONHIT,S_MONMISS,4,1,10,2,S_SHOOT,S_MONMISS,I_TROGLODYTE_PRIEST,False,False)
        elif code==MOB_WRAITH:
            m = Mob(code,'WRAITH','',True,50,5,False,True,False,True,True,False,True,False,False,0,False,True,False,False,20,40,False,True,False,0,7,[MA_GAZE_SCARE, MA_FLIT, MA_CHILL_DART],4,3,12,4,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_BAN_SIDHE,False,True)
        elif code==MOB_DARK_ELF_DRUID:
            m = Mob(code,'DARK ELF DRUID','',True,50,5,False,False,False,False,True,False,False,False,False,0,False,False,False,False,20,40,False,False,False,1,2,[MA_SUMMON_ANIMALS, MA_FLEE_SLOWLY],4,4,12,2,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_DARK_ELF_DRUID,False,False)
        elif code==MOB_NAGA_ARCHER:
            m = Mob(code,'NAGA ARCHER','',True,70,5,False,True,False,False,True,False,False,False,False,1,False,False,False,False,30,60,False,False,False,4,2,[MA_SNIPE_NAGA, MA_GAZE_SLEEP, MA_SUMMON_COBRAS],4,3,10,5,S_MONHIT,S_MONMISS,3,3,12,6,S_SHOOT,S_MONMISS,I_NAGA2,False,False)
        elif code==MOB_BIG_SPIDER:
            m = Mob(code,'SPIDER','',False,60,5,False,True,False,False,True,False,False,False,False,2,False,False,False,False,0,0,False,False,False,2,4,[MA_STING_PARALYSIS],3,3,10,3,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_BIG_SPIDER,False,False)
        elif code==MOB_TURTLOID:
            m = Mob(code,'TURTLOID','',False,90,5,False,False,False,False,False,False,False,False,False,0,False,False,False,False,0,0,False,False,False,6,0,[MA_ENTER_SHELL, MA_EXIT_SHELL],3,3,10,3,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_TURTLOID,True,True)
        elif code==MOB_WHITE_WOLF:
            m = Mob(code,'WHITE WOLF','',False,90,5,False,False,False,False,False,False,False,False,False,0,False,False,False,False,0,0,False,False,True,0,5,[MA_AURA_COOL, MA_HOWL],3,6,12,5,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_WHITE_WOLF,False,True)
        elif code==MOB_TROLL:
            m = Mob(code,'TROLL','',False,120,5,False,True,False,False,False,False,False,False,False,0,False,False,False,True,10,40,False,False,False,2,2,[MA_SMITE_OGRE],1.5,6,16,4,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_TROLL,False,False)
        elif code==MOB_NAGA_WARRIOR:
            m = Mob(code,'NAGA WARRIOR','',True,130,5,False,True,False,False,True,False,False,False,False,0,False,False,False,False,50,100,False,False,False,7,2,[MA_SUMMON_SNAKES, MA_GAZE_PARALYSIS, MA_SPIT, MA_SMITE_NAGA],4,10,20,4,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_NAGA,False,False)
        elif code==MOB_WIGHT:
            m = Mob(code,'WIGHT','',False,130,5,False,True,False,True,True,False,False,False,False,0,False,False,False,False,30,80,False,False,False,5,0,[],3,10,20,1,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_WIGHT,False,True)
        elif code==MOB_CLAY_GOLEM:
            m = Mob(code,'CLAY GOLEM','',False,140,5,False,False,False,False,True,True,True,True,False,0,False,False,False,False,0,0,True,False,True,8,0,[],3,10,20,1,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_CLAY_GOLEM,False,True)
        elif code==MOB_ORC_CHAMPION:
            m = Mob(code,'ORC CHAMPION','',False,140,5,False,True,False,False,False,False,False,False,False,0,False,False,False,False,10,40,False,False,False,4,4,[MA_SMITE_ORC_CHAMPION, MA_STUN, MA_FINISHING_BLOW, MA_RALLY_ORCS],3,8,16,5,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_ORC_CHAMPION,False,False)
        elif code==MOB_MINOTAUR:
            m = Mob(code,'MINOTAUR','',False,150,5,False,False,False,False,False,False,True,False,False,0,False,False,False,False,20,50,False,False,False,3,3,[MA_SMITE_OGRE, MA_FINISHING_BLOW, MA_CHARGE],4,10,20,3,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_MINOTAUR,True,False)
        elif code==MOB_OGRE:
            m = Mob(code,'OGRE','',False,180,5,False,True,False,False,False,False,False,False,False,0,False,False,False,False,10,40,False,False,False,4,1,[MA_SMITE_OGRE, MA_GET_CONFUSED, MA_SHOVE],5,5,15,1,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_OGRE,True,False)
        elif code==MOB_MAGEBANE_EYE:
            m = Mob(code,'MAGEBANE EYE','',True,40,6,False,False,False,False,False,False,False,False,False,0,False,False,False,False,0,0,False,False,True,0,0,[MA_ANTICASTER],0,0,0,0,0,0,0,0,0,0,0,0,I_EYE,False,False)
        elif code==MOB_ARTIFICER:
            m = Mob(code,'ARTIFICER','',True,50,6,False,True,False,False,False,False,False,False,False,0,False,False,False,False,50,150,False,False,False,0,2,[MA_SPELL_FIREBOLT, MA_SUMMON_GOLEM],0,0,0,0,0,0,0,0,0,0,0,0,I_ARTIFICER,False,False)
        elif code==MOB_DARK_ELF_ARCHER:
            m = Mob(code,'DARK ELF ARCHER','',True,50,6,False,True,False,False,False,False,False,False,False,0,False,False,False,False,30,80,False,False,False,0,5,[MA_SNIPE_DARK_ELF],3,6,12,6,S_MONHIT,S_MONMISS,2,4,14,7,S_SHOOT,S_MONMISS,I_DARK_ELF_ARCHER,True,False)
        elif code==MOB_MAGE:
            m = Mob(code,'MAGE','',True,50,6,False,True,False,False,False,False,False,False,False,0,False,False,False,False,40,120,False,False,False,0,3,[MA_SPELL_FIREBOLT, MA_SPELL_SCARE, MA_SPELL_PARALYSIS, MA_SPELL_SLEEP, MA_SUMMON_EVIL_TRASH, MA_SPELL_DEFLECT_MISSILES],0,0,0,0,0,0,4,1,20,4,S_ZAP,S_MONMISS,I_MAGE,False,False)
        elif code==MOB_LIME_JELLY:
            m = Mob(code,'LIME JELLY','',False,60,6,False,False,False,False,True,False,True,True,False,0,False,False,True,True,0,0,False,False,False,0,0,[],3,1,20,4,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_LIME_JELLY,False,False)
        elif code==MOB_DARK_PRIEST:
            m = Mob(code,'DARK PRIEST','',True,80,6,False,True,False,False,False,False,False,False,False,0,False,False,False,False,30,80,False,True,False,4,2,[MA_SPELL_HEAL, MA_SMITE_DARK_PRIEST, MA_SPELL_CAUSE_WOUNDS, MA_SPELL_SCARE, MA_SUMMON_EVIL_TRASH],3,10,20,1,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_DARK_PRIEST,False,False)
        elif code==MOB_MUTE_GHOST:
            m = Mob(code,'MUTE GHOST','',True,80,6,False,True,False,True,True,False,True,False,False,0,False,True,False,False,20,60,False,True,False,0,6,[MA_SILENCE, MA_GAZE_DRAIN_BAR, MA_GAZE_SCARE, MA_CHILL_DART],3,4,14,4,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_MUTE_GHOST,False,True)
        elif code==MOB_BURNING_GHOST:
            m = Mob(code,'BURNING GHOST','',True,95,6,False,True,False,True,True,True,True,False,False,0,True,True,False,False,20,60,False,True,False,0,6,[MA_FIREBOLT],3,6,16,4,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_BURNING_GHOST,False,False)
        elif code==MOB_SHADOW:
            m = Mob(code,'SHADOW','',True,95,6,False,True,False,True,True,False,True,False,False,0,False,True,False,False,20,60,False,True,True,0,5,[MA_DARKNESS, MA_FLIT, MA_SHADOW_BOLT],3,4,12,5,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_SHADOW,False,True)
        elif code==MOB_DARK_DWARF:
            m = Mob(code,'DARK DWARF','',False,110,6,False,True,False,False,True,True,False,False,False,0,False,False,False,False,40,120,False,False,False,12,1,[MA_SCOTS, MA_DWARF_SPIRITS],4,6,18,5,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_DARK_DWARF,False,False)
        elif code==MOB_MUMMY:
            m = Mob(code,'MUMMY','Curse causing aggro, and multiplying all received damage by a lot. Try to make sure it gets one off before being exterminated',True,120,6,False,True,False,True,True,False,False,False,False,0,False,False,False,False,40,200,False,False,False,2,0,[MA_CURSE, MA_SUMMON_LOW_UNDEAD, MA_SPELL_SCARE],4,4,14,4,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_MUMMY,False,True)
        elif code==MOB_BASILISK:
            m = Mob(code,'BASILISK','',False,140,6,False,False,False,False,True,False,False,False,False,1,False,False,False,False,40,100,False,False,True,6,0,[MA_GAZE_PARALYSIS_OFTEN],3,6,12,4,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_BASILISK,False,False)
        elif code==MOB_BLACK_KNIGHT:
            m = Mob(code,'BLACK KNIGHT','',False,150,6,False,True,False,False,False,False,False,False,False,0,False,False,False,False,50,150,False,True,False,6,6,[MA_BLOCK, MA_SMITE_BLACK_KNIGHT, MA_STUN, MA_FINISHING_BLOW],3,12,20,7,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_BLACK_KNIGHT,False,False)
        elif code==MOB_MANTICORE:
            m = Mob(code,'MANTICORE','',True,150,6,False,False,False,False,False,False,False,False,False,0,False,False,False,False,40,120,False,True,False,2,5,[MA_SNIPE_MANTICORE, MA_FLY],3,12,20,4,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_MANTICORE,True,False)
        elif code==MOB_TROGLODYTE_BEASTMASTER:
            m = Mob(code,'TROGLODYTE BEASTMASTER','',True,200,6,False,True,False,False,True,False,False,False,False,1,False,False,False,False,30,80,False,False,False,4,3,[MA_SUMMON_TROGLODYTES, MA_SUMMON_ANIMALS, MA_SPELL_POISON_CLOUD],3,6,10,6,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_BEASTMASTER,False,False)
        elif code==MOB_BLACK_OGRE:
            m = Mob(code,'BLACK OGRE','',False,210,6,False,True,False,False,False,False,True,False,False,0,False,False,False,False,30,80,False,False,False,6,3,[MA_SMITE_BLACK_OGRE, MA_SHOVE],3,15,30,1,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_BLACK_OGRE,True,False)
        elif code==MOB_SUMMONER:
            m = Mob(code,'SUMMONER','',True,80,7,False,True,False,False,False,False,False,False,False,0,False,False,False,False,50,150,False,False,False,2,2,[MA_SUMMON_VARIOUS,MA_SUMMON_BODYGUARD, MA_FLEE_SLOWLY],0,0,0,0,0,0,0,0,0,0,0,0,I_SUMMONER,False,False)
        elif code==MOB_WARLOCK:
            m = Mob(code,'WARLOCK','',True,80,7,False,False,False,False,False,False,False,False,False,0,False,False,False,False,50,150,False,False,False,4,4,[MA_SPELL_LIGHTNING, MA_SPELL_POISON_CLOUD, MA_SPELL_FIREBOLT, MA_SPELL_CAUSE_WOUNDS],0,0,0,0,0,0,3,5,15,6,S_ZAP,S_MONMISS,I_WARLOCK,False,False)
        elif code==MOB_BAN_SIDHE:
            m = Mob(code,'BAN SIDHE','',True,84,7,False,True,False,True,True,False,True,False,False,0,False,True,False,False,50,150,False,True,True,0,10,[MA_SHRIEK_SCARE, MA_SUMMON_UNDEAD, MA_SHADOW_BOLT],3,2,8,4,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_WRAITH,False,True)
        elif code==MOB_BERSERKER:
            m = Mob(code,'BERSERKER','',False,210,7,False,False,False,False,False,False,False,False,False,0,False,False,False,True,0,0,False,False,False,2,2,[MA_GET_CONFUSED, MA_CLEAVE, MA_SMITE_BERSERKER],2,6,16,3,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_BERSERKER,False,False)
        elif code==MOB_HELLHOUND:
            m = Mob(code,'HELLHOUND','',False,250,7,False,True,False,False,True,True,False,False,False,0,False,False,False,False,0,0,False,False,True,3,3,[MA_BREATH_SMALL_FIRE],0,0,0,0,0,0,0,0,0,0,0,0,I_HELLHOUND,False,False)
        elif code==MOB_HILL_GIANT:
            m = Mob(code,'HILL GIANT','',True,280,7,False,True,False,False,False,False,True,False,False,0,False,False,False,False,40,120,False,False,False,8,2,[MA_SUPER_SMITE, MA_GET_CONFUSED, MA_HURL_AND_ADVANCE, MA_SHOVE],4,15,30,1,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_HILL_GIANT,True,False)
        elif code==MOB_STONE_GOLEM:
            m = Mob(code,'STONE GOLEM','',False,300,7,False,False,False,False,True,True,True,True,False,0,False,False,False,False,0,0,True,False,True,14,0,[],3,15,30,2,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_STONE_GOLEM,True,True)
        elif code==MOB_YOUNG_RED_DRAGON:
            m = Mob(code,'YOUNG RED DRAGON','',True,384,7,True,True,True,False,False,True,True,False,False,0,False,False,False,False,0,0,False,True,True,8,0,[MA_BREATH_FIRE, MA_TAILSWIPE, MA_FLY, MA_FLIT, MA_CLAW, MA_AURA_FIRE, MA_SPELL_SCARE],0,0,0,0,0,0,0,0,0,0,0,0,I_DRAGON,True,False)
        elif code==MOB_DARK_DWARF_LAIRD:
            m = Mob(code,'DARK DWARF LAIRD','',False,350,7,False,True,False,False,True,True,True,False,False,0,False,False,False,False,100,200,False,False,False,12,1,[MA_BURNS, MA_BLOCK, MA_STUN],3,8,18,2,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_DARK_DWARF_LAIRD,False,False)
        elif code==MOB_ORC_BOSS:
            m = Mob(code,'ORC BOSS','',True,420,7,True,True,False,False,False,False,True,False,False,0,False,False,False,False,0,0,False,True,True,4,4,[MA_SMITE_ORC_BOSS, MA_SNIPE_ORC_BOSS, MA_SUMMON_ORCS, MA_STUN],1.5,8,18,6,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_ORC_BOSS,True,False)
        elif code==MOB_SORCERER:
            m = Mob(code,'SORCERER','',True,140,8,False,True,False,False,False,False,False,False,False,0,False,False,False,False,100,200,False,False,False,3,6,[MA_SPELL_LIGHTNING, MA_SPELL_POISON_CLOUD, MA_SPELL_FIREBOLT, MA_SPELL_PARALYSIS, MA_SPELL_SLEEP, MA_SUMMON_VARIOUS, MA_SPELL_DEFLECT_MISSILES, MA_SPELL_INVULNERABILITY, MA_SUMMON_FRONT, MA_FLEE_SLOWLY],0,0,0,0,0,0,3,1,24,4,S_ZAP,S_MONMISS,I_SORCERER,False,False)
        elif code==MOB_AIR_ELEMENTAL:
            m = Mob(code,'AIR ELEMENTAL','',True,250,8,False,False,False,False,True,True,True,False,True,0,False,False,False,False,50,150,False,True,False,0,12,[MA_TORNADO, MA_FLIT, MA_SNIPE_POLTERGEIST],3,8,20,7,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_AIR_ELEMENTAL,True,True)
        elif code==MOB_MAULER:
            m = Mob(code,'MAULER','',False,280,8,False,True,False,False,True,True,True,False,False,0,False,False,False,False,100,200,False,False,True,6,6,[],0.8,10,20,7,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_CLAWED_DEMON,False,False)
        elif code==MOB_DARK_ELF_BOSS:
            m = Mob(code,'DARK ELF BOSS','',True,350,8,True,True,False,False,True,False,True,False,False,0,False,False,False,False,0,0,False,True,True,2,9,[MA_BLOCK, MA_SPELL_LIGHTNING, MA_LEAP_AWAY, MA_SPELL_PARALYSIS, MA_STUN, MA_SMITE_DARK_ELF, MA_SNIPE_DARK_ELF],2,6,16,10,S_MONHIT,S_MONMISS,3,5,15,7,S_SHOOT,S_MONMISS,I_DARK_ELF_BOSS,False,False)
        elif code==MOB_FIRE_ELEMENTAL:
            m = Mob(code,'FIRE ELEMENTAL','',False,360,8,False,False,False,False,True,True,True,False,False,0,True,False,False,False,50,150,False,False,False,0,2,[MA_SOFT_ENRAGE, MA_AURA_FIRE],2,12,24,5,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_FIRE_ELEMENTAL,True,False)
        elif code==MOB_YOUNG_GREEN_DRAGON:
            m = Mob(code,'YOUNG GREEN DRAGON','',True,490,8,True,True,True,False,True,False,True,False,False,1,False,False,False,False,0,0,False,True,True,8,0,[MA_BREATH_POISON_CLOUD, MA_TAILSWIPE, MA_FLY, MA_FLIT, MA_CLAW, MA_SPELL_SCARE, MA_SPELL_SILENCE, MA_BITE],0,0,0,0,0,0,0,0,0,0,0,0,I_GREEN_DRAGON,True,False)
        elif code==MOB_SNAKE_BOSS:
            m = Mob(code,'SNAKE BOSS','',True,560,8,True,True,False,False,True,False,False,False,False,2,False,False,False,False,0,0,False,True,True,6,3,[MA_SUMMON_REPTILES, MA_GAZE_PARALYSIS, MA_GRAPPLE, MA_SQUEEZE_NASTY],3,10,20,8,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_SNAKE_BOSS,True,False)
        elif code==MOB_UNDEAD_BOSS:
            m = Mob(code,'UNDEAD BOSS','',True,480,9,True,True,False,True,True,False,True,True,False,0,False,True,False,False,0,0,False,True,True,0,8,[MA_FLY, MA_SUMMON_UNDEAD, MA_METEOR_SWARM, MA_SPELL_LIGHTNING, MA_SPELL_POISON_CLOUD, MA_SPELL_DEFLECT_MISSILES, MA_SPELL_INVULNERABILITY, MA_AURA_COLD],2,2,12,7,S_MONHIT,S_MONMISS,0,0,0,0,0,0,I_UNDEAD_BOSS,True,True)
        elif code==MOB_YOUNG_BLUE_DRAGON:
            m = Mob(code,'BLUE DRAGON','Not sure what the grapple does',True,640,9,True,True,True,False,False,False,True,False,True,0,False,False,False,False,0,0,False,True,True,8,0,[MA_BREATH_LIGHTNING, MA_TAILSWIPE, MA_FLY, MA_FLIT, MA_CLAW_FAST, MA_BITE, MA_GRAPPLE, MA_DROP_BOULDER],0,0,0,0,0,0,0,0,0,0,0,0,I_BLUE_DRAGON,True,False)
        else:
            print code
        return m

    def push_monster_forward(self,level):
        push_to = self.last_free_front_pos()
        if push_to is None:
            return False
        min_pushable_level = level
        to_push = None
        for pos in [POS_M_BACK_1,POS_M_BACK_2,POS_M_BACK_3]:
            m = self.get_monster_at_pos(pos)
            if m is None:
                continue
            if not m.prefer_back:
                if m.about_to_die:
                    m.die()
                else:
                    m.slide_from = m.pos
                    m.slide_to = push_to
                    m.slide_start = w.elapsed_since_beginning
                    m.slide_finish = w.elapsed_since_beginning + 0.1
                    m.pos = push_to
                return True
            if m.level < min_pushable_level or m.about_to_die:
                min_pushable_level = m.level
                to_push = m
        if to_push is not None:
            if to_push.about_to_die:
                to_push.die()
            else:
                to_push.slide_from = to_push.pos
                to_push.slide_to = push_to
                to_push.slide_start = w.elapsed_since_beginning
                to_push.slide_finish = w.elapsed_since_beginning + 0.1
                to_push.pos = push_to
            return True
        return False

    def push_monster_back(self,level):
        push_to = self.last_free_back_pos()
        if push_to is None:
            return False
        min_pushable_level = level
        to_push = None
        for pos in [POS_M_FRONT_1,POS_M_FRONT_2,POS_M_FRONT_3]:
            m = self.get_monster_at_pos(pos)
            if m is None:
                continue
            if m.prefer_back:
                if m.about_to_die:
                    m.die()
                else:
                    m.slide_from = m.pos
                    m.slide_to = push_to
                    m.slide_start = w.elapsed_since_beginning
                    m.slide_finish = w.elapsed_since_beginning + 0.1
                    m.pos = push_to
                return True
            if m.level < min_pushable_level or m.about_to_die:
                min_pushable_level = m.level
                to_push = m
        if to_push is not None:
            if to_push.about_to_die:
                to_push.die()
            else:
                to_push.slide_from = to_push.pos
                to_push.slide_to = push_to
                to_push.slide_start = w.elapsed_since_beginning
                to_push.slide_finish = w.elapsed_since_beginning + 0.1
                to_push.pos = push_to
            return True
        return False

    def random_free_back_pos(self):
        options = []
        if self.get_monster_at_pos(POS_M_BACK_1) is None:
            options.append(POS_M_BACK_1)
        if self.get_monster_at_pos(POS_M_BACK_2) is None:
            options.append(POS_M_BACK_2)
        if self.get_monster_at_pos(POS_M_BACK_3) is None:
            options.append(POS_M_BACK_3)
        if len(options)>0:
            return choice(options)
        return None

    def random_free_front_pos(self):
        options = []
        if self.get_monster_at_pos(POS_M_FRONT_1) is None:
            options.append(POS_M_FRONT_1)
        if self.get_monster_at_pos(POS_M_FRONT_2) is None:
            options.append(POS_M_FRONT_2)
        if self.get_monster_at_pos(POS_M_FRONT_3) is None:
            options.append(POS_M_FRONT_3)
        if len(options)>0:
            return choice(options)
        return None
    
    def last_free_back_pos(self):
        if self.get_monster_at_pos(POS_M_BACK_1) is None:
            return POS_M_BACK_1
        if self.get_monster_at_pos(POS_M_BACK_2) is None:
            return POS_M_BACK_2
        if self.get_monster_at_pos(POS_M_BACK_3) is None:
            return POS_M_BACK_3
        return None

    def last_free_front_pos(self):
        if self.get_monster_at_pos(POS_M_FRONT_1) is None:
            return POS_M_FRONT_1
        if self.get_monster_at_pos(POS_M_FRONT_2) is None:
            return POS_M_FRONT_2
        if self.get_monster_at_pos(POS_M_FRONT_3) is None:
            return POS_M_FRONT_3
        return None

    def n_front_mobs_taking_up_space(self):
        count = 0
        for m in self.mobs:
            if m.pos is not None and m.alive and m.front_row():
                count = count + 1
        return count

    def n_mobs_taking_up_space(self):
        count = 0
        for m in self.mobs:
            if m.pos is not None and m.alive:
                count = count + 1
        return count
    
    def n_surviving_mobs(self):
        count = 0
        for m in self.mobs:
            if m.pos is not None and m.alive and not m.about_to_die:
                count = count + 1
        return count

    def n_surviving_front_mobs(self):
        count = 0
        for m in self.mobs:
            if m.pos is not None and m.front_row() and m.alive and not m.about_to_die:
                count = count + 1
        return count

    def n_surviving_back_mobs(self):
        count = 0
        for m in self.mobs:
            if m.pos is not None and (not m.front_row()) and m.alive and not m.about_to_die:
                count = count + 1
        return count

    def random_target_from(self,posses,hated_by):
        options = []
        preferences = []
        ruthless = False
        if hated_by is not None:
            for this_action in hated_by.actions:
                if this_action.action_code==CA_RUTHLESS:
                    ruthless = True
                    break
        for pos in posses:
            t = self.get_monster_at_pos(pos)
            if t is not None and t.alive and not t.about_to_die:
                options.append(t)
                if hated_by is not None and opposite_sides(hated_by.pos,t.pos):
                    this_preference = 0.1
                else:
                    this_preference = 1.0
                if ruthless:
                    this_preference = this_preference / (3.0 + t.health)
                if t.vendetta:
                    this_preference = this_preference * 5.0
                preferences.append(this_preference)
        if len(options)==0:
            return None
        cumulative_preferences = []
        so_far = 0
        for preference in preferences:
            cumulative_preferences.append(so_far + float(preference) / sum(preferences))
            so_far = so_far + float(preference) / sum(preferences)
        u = uniform(0,1)
        for i in range(len(preferences)):
            if cumulative_preferences[i] >= u:
                return options[i]

    def random_target(self,hated_by):
        return self.random_target_from([POS_M_FRONT_1,POS_M_FRONT_2,POS_M_FRONT_3,POS_M_BACK_1,POS_M_BACK_2,POS_M_BACK_3],hated_by)

    def random_front_target(self,hated_by):
        return self.random_target_from([POS_M_FRONT_1,POS_M_FRONT_2,POS_M_FRONT_3],hated_by)

    def random_back_target(self,hated_by):
        return self.random_target_from([POS_M_BACK_1,POS_M_BACK_2,POS_M_BACK_3],hated_by)

    def weakest_target(self):
        min_level = 9999
        min_hp = 9999
        result = None
        for pos in [POS_M_FRONT_1,POS_M_FRONT_2,POS_M_FRONT_3,POS_M_BACK_1,POS_M_BACK_2,POS_M_BACK_3]:
            t = self.get_monster_at_pos(pos)
            if t is not None:
                if t.level < min_level:
                    result = t
                elif t.level==min_level and t.health<min_hp:
                    result = t
        return result
        
    def flush(self):
        for m in self.mobs:
            m.pos = None
            m.alive = False
            m.about_to_die = False            
        
class World():
    def __init__(self):
        self.initialise()

    def initialise(self):
        self.begin = pygame.time.get_ticks()
        self.now = pygame.time.get_ticks()
        self.since_last_process = self.begin
        self.elapsed_since_beginning = 0
        self.can_flee = True
        self.party_fleeing = False
        self.start_fleeing_at = None
        self.n_fled = 0
        self.waiting_gold = 0
        self.clear = True
        self.clear_as_of = self.begin
        self.collected_gold = 0
        self.items_found = []
        self.evil_kills = []
        self.end_at = None
        self.event_no = 0
        self.started_pause_at = None
        self.started_respite_at = None
        self.traps_here = False
        self.no_fly = False
        self.mobs_will_leave = False
        self.mobs_arrived = 0
        self.dungeon_won = False
        self.darkness_at = None
        self.darkness = 0
        self.silence_until = 0
        self.slow_time_until = 0
        self.poison_cloud_until = 0
        self.poison_cloud_from = None
        self.mob_queue = []
        self.resetting = False
        
    def reset(self,scenario):
        self.initialise()
        self.scenario = scenario()

    def find_gold(self,n):
        d.sound_queue.append(S_GOLD)
        increase = 1.0
        for c in p.chars:
            if c.alive and not c.about_to_die:
                for a in c.actions:
                    if a.action_code==CA_MORE_GOLD:
                        increase = max(increase, a.increase())
        n = int(round(n * increase))
        d.log('Found ' + str(n) + ' gold')
        d.message_queue.append(('Found '+str(n),I_GOLD,COL_LIGHT))
        self.collected_gold = self.collected_gold + n
        energy_gain = int(floor(0.2 * n))
        d.log(str(energy_gain) + ' bar for gold collected')
        n_awarded = 0
        failed = False
        while (n_awarded < energy_gain and not failed):
            failed = True
            for c in p.chars:
                if c.alive and not c.about_to_die and c.energy < c.max_energy and c.energy_for_gold and n_awarded < energy_gain:
                    c.energy = c.energy + 1
                    n_awarded = n_awarded + 1
                    failed = False

    def find_gems(self,n):
        d.sound_queue.append(S_GOLD)
        increase = 1.0
        for c in p.chars:
            if c.alive and not c.about_to_die:
                for a in c.actions:
                    if a.action_code==CA_MORE_GEMS:
                        increase = max(increase, a.increase())
        n = int(round(n * increase))
        d.log('Found gems worth ' + str(n))
        d.message_queue.append(('Found '+str(n),I_GEMS,COL_LIGHT))
        self.collected_gold = self.collected_gold + n

    def process(self):
        # How much real time has elapsed
        if self.slow_time_until > self.elapsed_since_beginning:
            elapsed_since_last_process = 0.5 * TIME_SCALAR * (pygame.time.get_ticks() - self.since_last_process)/1000.0
        else:
            elapsed_since_last_process = TIME_SCALAR * (pygame.time.get_ticks() - self.since_last_process)/1000.0
        self.since_last_process = pygame.time.get_ticks()
        self.elapsed_since_beginning = self.elapsed_since_beginning + elapsed_since_last_process
        if self.since_last_process == 0:
            return
        # The only time variables that should be used below here are elapsed_since_last_process and self.elapsed_since_beginning
        # World effects
        if self.darkness_at is not None:
            if (self.darkness > 3 and self.darkness_at < (self.elapsed_since_beginning - 2.0)) or (self.darkness > 0 and self.darkness_at < (self.elapsed_since_beginning - 5.0)):
                if self.darkness<=1:
                    self.darkness = 0
                    self.darkness_at = None
                else:
                    self.darkness = self.darkness - 1
                    self.darkness_at = self.elapsed_since_beginning
        if self.poison_cloud_until > self.elapsed_since_beginning:
            caster = self.poison_cloud_from
            if caster is not None and not caster.alive:
                self.poison_cloud_until = 0
            else:
                for c in p.chars:
                    if c.alive and not c.about_to_die and c.front_row():
                        if one_in(floor(1.0 / elapsed_since_last_process)):
                            if c.has_status_effect(EF_RESIST_POISON):
                                if one_in(3):
                                    c.take_damage(1)
                                    c.flash_image = I_POISON_CLOUD
                            else:
                                c.take_damage(1)
                                c.flash_image = I_POISON_CLOUD
                                if not c.has_status_effect(EF_POISON):
                                    poison_level = 1
                                else:
                                    poison_level = c.status_effects[EF_POISON].level
                                c.status_effects[EF_POISON] = Effect_parameters(10) 
                                c.status_effects[EF_POISON].level = poison_level                                           
        # Hand out money if all the mobs are dead
        if self.clear and self.waiting_gold>0 and not self.party_fleeing:
            if e.n_surviving_mobs()>0:
                self.clear = False
            elif self.elapsed_since_beginning > self.clear_as_of + 0.1:
                self.find_gold(self.waiting_gold)                
                self.waiting_gold = 0
        # any new mobs waiting to arrive?
        while e.n_mobs_taking_up_space()<6 and len(self.mob_queue)>0:
            mob_code = self.mob_queue[-1]
            self.mob_queue = self.mob_queue[:-1]
            m = e.add_mob(mob_code)
            if m is not None:
                d.log('Arrived: ' + m.name)
        # now loop through characters
        for c in p.chars:
            # any chars in the process of dying?
            if c.about_to_die and self.elapsed_since_beginning > c.die_at:
                c.die()
            # any stances wear off?
            if c.current_stance is not None:
                if c.stance_wears_off is not None and ((c.stance_wears_off < self.elapsed_since_beginning) or (c.current_stance==ST_RETRENCH and c.energy==c.max_energy)):
                    if c.current_stance==ST_BERSERK:
                        d.message_queue.append(('Has calmed down...',c.image,COL_LIGHT))
                    c.current_stance = None                        
            # counter attack from a Riposte?
            if c.current_stance is not None and c.current_stance==ST_COUNTER:
                target = c.counter_against
                c.current_stance = None
                if target is not None and target.alive and not target.about_to_die:
                   if c.melee_hit_roll(target):
                        d.sound_queue.append(c.melee_hit_sound)
                        damage = c.melee_damage_roll(target)
                        armor = target.current_armor()
                        if c.has_status_effect(EF_FIND_WEAKNESS):
                            armor = max(0,armor-5)
                        if damage > armor:
                            d.log(c.name + ' hits ' + target.name)
                            c.flash_image = I_PALE
                            target.take_damage(damage-armor)
                            if c.energy_for_hit:
                                c.energy = min(c.max_energy,c.energy + 2)
                        else:
                            d.log(c.name + ' hits ' + target.name + ' for 0')                    
            # any delayed actions ready to resolve?
            if c.alive and not c.about_to_die and c.action_in_progress is not None:
                if c.action_fires < self.elapsed_since_beginning:
                    c.action_in_progress.resolve(c.target_of_action_in_progress)
                    c.action_in_progress = None
            # anyone want to act on reflex?
            if c.alive and not c.about_to_die and c.dumb_until < self.elapsed_since_beginning and c.action_in_progress is None and c.current_stance is None and not w.dungeon_won:
                if not (c.has_status_effect(EF_PARALYSIS)) and not (c.has_status_effect(EF_STUN)) and not (c.has_status_effect(EF_FEAR)) and not (c.has_status_effect(EF_GRAPPLE)) and not (c.has_status_effect(EF_SLEEP)):
                    for a in c.actions:
                        if a.needed() and a.ok():
                            if randint(1,100)<=reflex_chance(c.reflexes,c.level,a.reflex_level()):
                                d.message_queue.append(('Uses ' + a.name(),c.image,COL_LIGHT))
                                a.trigger(a.reflex_target())
                                c.flash_image = I_VERYPALE
                                if a.name()<>'':
                                    d.log(c.name + ' chooses ' + a.name() + ' by reflex')
                            else:
                                c.dumb_until = self.elapsed_since_beginning + 2
                            break
            # regenerate bar (or berserk warriors regenerate health instead)
            if c.energy_gain_period is not None:
                if self.elapsed_since_beginning > c.last_energy_gain + c.energy_gain_period:
                    if not (c.has_status_effect(EF_STUN) or c.has_status_effect(EF_PARALYSIS) or c.current_stance==ST_BERSERK):
                        c.last_energy_gain = self.elapsed_since_beginning
                        gain_amount = 1
                        if c.current_stance==ST_RETRENCH:
                            gain_amount = gain_amount * 3
                        c.energy = min(c.max_energy,c.energy + gain_amount)
                        if c.current_stance==ST_BERSERK:
                            c.health = min(c.max_health, c.health+2)
            # poison hurts
            if c.has_status_effect(EF_POISON) and c.status_effects[EF_POISON].level>0:
                if c.status_effects[EF_POISON].lasts_until <= (self.elapsed_since_beginning + 0.5):
                    if c.status_effects[EF_POISON].level>1:
                        c.status_effects[EF_POISON].lasts_until = self.elapsed_since_beginning + 10
                    c.status_effects[EF_POISON].level = c.status_effects[EF_POISON].level-1
                else:
                    if one_in(floor((3.0/c.status_effects[EF_POISON].level) / elapsed_since_last_process)):
                        c.take_damage(1)
            # run away!
            if c.has_status_effect(EF_FEAR) and c.can_act() and c.front_row():
                if one_in(floor(3.0 / elapsed_since_last_process)):
                    d.message_queue.append(('Aaaaaargh!',c.image,COL_LIGHT))
                    if not p.move_character(c):
                        p.move_character_to(c,choice([POS_C_BACK_1,POS_C_BACK_2,POS_C_BACK_3]))
        for m in e.mobs:
            # any mobs in the process of dying?
            if m.about_to_die and self.elapsed_since_beginning > m.die_at:
                m.die()
            # any delayed mob actions ready to resolve?
            if m.alive and not m.about_to_die and m.action_in_progress is not None:
                if m.action_fires < self.elapsed_since_beginning:
                    if m.target_of_action_in_progress is not None and len(m.action_in_progress.valid_targets())>0:
                        if m.target_of_action_in_progress not in m.action_in_progress.valid_targets():
                            m.target_of_action_in_progress = choice(m.action_in_progress.valid_targets())
                    m.action_in_progress.resolve(m.target_of_action_in_progress)
                    m.action_in_progress.last_used = self.elapsed_since_beginning
                    m.action_in_progress = None
        # what happens next in the scenario?
        must_pause = False
        while (self.event_no < len(self.scenario) and must_pause is False):
            this_event = self.scenario[self.event_no]
            # if we are flying, we may bypass this
            flying = False
            for c in p.chars:
                if c.alive and not c.about_to_die and c.current_stance==ST_FLY:
                    flying = True
                    image = c.image
            if flying:
                if this_event[0]==DE_NOFLY or this_event[0]==DE_ALIGHT or this_event[0]==DE_QUIT:
                    # horrible kludge
                    d.print_message('You rise into the air!',image,COL_LIGHT)
                    d.redraw(p,e,self)
                    self.wait(4000)
                    for c in p.chars:
                        if c.alive and not c.about_to_die and c.current_stance==ST_FLY:
                            c.current_stance = None                   
                    d.print_message('You alight...',image,COL_LIGHT)
                    d.redraw(p,e,self)
                    self.wait(1000)
                else:
                    self.started_pause_at = None
                    self.started_respite_at = None
                    self.event_no = self.event_no + 1
                    continue
            # are we in the middle of a pause?
            if this_event[0]==DE_PAUSE:
                if self.started_pause_at is None:
                    self.started_pause_at = self.elapsed_since_beginning
                if self.started_respite_at is None and e.n_surviving_mobs()==0:
                    self.started_respite_at = self.elapsed_since_beginning
                if (self.started_pause_at >= self.elapsed_since_beginning - this_event[1]):
                    if (self.started_respite_at is None or this_event[2] is None or (self.started_respite_at >= self.elapsed_since_beginning - this_event[2])):
                        must_pause = True
                if must_pause==False:                  
                    self.started_pause_at = None
                    self.started_respite_at = None
                    self.event_no = self.event_no + 1                         
            else:
                # do one or more events
                if this_event[0]==DE_MOBS:
                    self.mobs_arrived = self.elapsed_since_beginning
                    #while e.n_surviving_mobs()>(6 - this_event[2]):
                    #    e.weakest_target().flee()
                    for i in range(this_event[2]):
                        m = e.add_mob(this_event[1])
                        if m is not None:
                            d.log('Arrived: ' + m.name)
                        else:
                            self.mob_queue.append(this_event[1])
 #                   if m is not None:
 #                       if this_event[2]>1:
 #                           d.message_queue.append(('Arrived (x' + str(this_event[2]) + ')',m.image,COL_LIGHT))
 #                       else:
 #                           d.message_queue.append(('Arrived',m.image,COL_LIGHT))
                    # are we stealthy?
                    stealthy = False
                    for c in p.chars:
                        if c.alive and not c.about_to_die and c.current_stance==ST_STEALTH:
                            worked = True
                            for a in c.actions:
                                if a.action_code==CA_STEALTH:
                                    if randint(1,100) > a.chance():
                                        worked = False
                            if worked:
                                self.mobs_will_leave = True
                            else:
                                d.message_queue.append(('You are not sneaky enough!',None,COL_LIGHT))
                            for c2 in p.chars:
                                if c2.current_stance==ST_STEALTH:
                                    c2.current_stance = None
                            continue
                    if self.mobs_will_leave:
                        for m in e.mobs:
                            if m.alive and not m.about_to_die and m.perceptive:
                                d.message_queue.append(('You are discovered!',None,COL_LIGHT))
                                self.mobs_will_leave = False
                                continue
                elif this_event[0]==DE_MESSAGE:
                    d.message_queue.append([this_event[2],this_event[1],COL_LIGHT])
                elif this_event[0]==DE_DOOR:
                    d.message_queue.append(['You open a door...',I_DOOR,COL_LIGHT])
                    d.sound_queue.append(S_DOOR)
                elif this_event[0]==DE_STAIRS:
                    d.message_queue.append(['You descend...',I_STAIRS,COL_LIGHT])
                    d.sound_queue.append(S_STAIRS)
                elif this_event[0]==DE_NOFLEE:
                    d.message_queue.append(('You can no longer flee. Win or perish!',None,COL_LIGHT))
                    self.can_flee = False
                    d.log('Party can no longer flee')
                elif this_event[0]==DE_CLEARMOBS:
                    if e.n_surviving_mobs()>0:
                        d.message_queue.append(('All the monsters flee!',None,COL_LIGHT));
                        e.flush()
                        d.log('All monsters flushed')
                elif this_event[0]==DE_GOLD:
                    self.find_gold(randint(this_event[1],this_event[2]))
                elif this_event[0]==DE_GEMS:
                    self.find_gems(randint(this_event[1],this_event[2]))
                elif this_event[0]==DE_WIN:
                    d.log('Dungeon complete')
                    d.message_queue.append(('You have beaten this dungeon!',I_CROWN,COL_LIGHT))
                    d.sound_queue.append(S_FANFARE)
                    self.dungeon_won = True
                elif this_event[0]==DE_QUIT:
                    d.log('End of scenario')
                    d.quit_now = True
                    xp = 0
                    for c in p.chars:
                        if c.alive and not c.about_to_die:
                            xp = xp + c.xp
                    d.print_message('Escaped with ' + str(xp) + ' XP and ' + str(self.collected_gold) + ' gold',I_GOLD,COL_LIGHT)
                    self.wait(4000)
                elif this_event[0]==DE_CHECKTRAPS:
                    self.traps_here = True
                elif this_event[0]==DE_PIT or this_event[0]==DE_EXPLOSIVERUNE or this_event[0]==DE_GASTRAP or this_event[0]==DE_SUMMONINGRUNE or this_event[0]==DE_SPIKEDPIT:
                    self.traps_here = False
                    escape = False
                    for c in p.chars:
                        if c.alive and not c.about_to_die:
                            if c.has_status_effect(EF_REVEAL_DANGER):
                                escape = True
                    if escape:
                        d.message_queue.append(('You evade a trap!',I_PIT,COL_LIGHT))
                        d.log('Evade a trap')
                    else:
                        if this_event[0]==DE_PIT:
                            d.log('Fall into a pit!')
                            a = p.random_front_target(None)
                            d.message_queue.append((a.name + ' has fallen into a pit!',I_PIT,COL_LIGHT))
                            a.take_damage(randint(5,15))                        
                        elif this_event[0]==DE_EXPLOSIVERUNE:
                            d.log('Set off an explosive rune!')
                            d.message_queue.append(('You have set off an explosive rune!',I_EXPLOSIVE_RUNE,COL_LIGHT))
                            d.sound_queue.append(S_ZAP)
                            for pos in [POS_C_FRONT_1,POS_C_FRONT_2,POS_C_FRONT_3]:
                                a = p.get_char_at_pos(pos)
                                if a is not None:
                                    a.take_damage(randint(3,7))
                        elif this_event[0]==DE_GASTRAP:
                            d.log('Set off a gas trap!')
                            d.message_queue.append(('You have set off a gas trap!',I_GAS_TRAP,COL_LIGHT))
                            d.sound_queue.append(S_GAS)
                            w.poison_cloud_until = w.elapsed_since_beginning + 8.0
                            w.poison_cloud_from = None
                        elif this_event[0]==DE_SUMMONINGRUNE:
                            d.log('Set off a summoning rune!')
                            d.message_queue.append(('You have set off a summoning rune!',I_SUMMONING_RUNE,COL_LIGHT))
                            d.sound_queue.append(S_BAD_SPELL)
                            summoned = 0
                            to_summon = 3
                            for i in range(to_summon):
                                options = [MOB_BLAST_SPORE,MOB_COBRA,MOB_GIANT_FIREFLY,MOB_GIANT_WASP,MOB_JELLY,MOB_MILLIPEDE,MOB_ORC_WARRIOR,MOB_PYTHON,MOB_SKELETON,MOB_TROGLODYTE_ARCHER,MOB_WOLF,MOB_LEPRECHAUN,MOB_OOZE,MOB_ORC_AGITATOR,MOB_ORC_SLAVEMASTER,MOB_ORC_WITH_MANCATCHER,MOB_SPECTRE,MOB_TROGLODYTE_SHAMAN,MOB_CLAY_GOLEM,MOB_NAGA_ARCHER,MOB_NAGA_WARRIOR,MOB_OGRE,MOB_ORC_CHAMPION,MOB_TROGLODYTE_PRIEST,MOB_TURTLOID,MOB_WHITE_WOLF,MOB_WIGHT,MOB_WRAITH,MOB_ARTIFICER,MOB_BASILISK,MOB_BLACK_KNIGHT,MOB_BLACK_OGRE,MOB_BURNING_GHOST,MOB_DARK_DWARF,MOB_DARK_ELF_ARCHER,MOB_DARK_PRIEST,MOB_LIME_JELLY,MOB_MAGE,MOB_MAGEBANE_EYE,MOB_MUTE_GHOST,MOB_SHADOW]
                                new_critter = e.add_mob(choice(options))
                                if new_critter is not None and new_critter.level<=this_event[1]:
                                    new_critter.from_summoning_trap = True
                                    new_critter.slide_from = None
                                    new_critter.fade_in = True
                                    new_critter.fade_start = w.elapsed_since_beginning
                                    new_critter.fade_finish = w.elapsed_since_beginning + 0.5
                                    summoned = summoned + 1
                        elif this_event[0]==DE_SPIKEDPIT:
                            d.log('Fall into a spiked pit!')
                            a = p.random_front_target(None)
                            d.message_queue.append((a.name + ' has fallen into a spiked pit!',I_SPIKED_PIT,COL_LIGHT))
                            a.take_damage(randint(10,30))  
                elif this_event[0]==DE_LOOT:
                    item = make_item(this_event[1])
                    self.items_found.append(this_event[1])
                    d.message_queue.append(('Found: '+ item.name(),I_LOOT,COL_LIGHT))
                    d.sound_queue.append(S_ITEM)
                elif this_event[0]==DE_GOLDCHEST:
                    d.print_message('You have found a chest!',I_GOLD_CHEST,COL_LIGHT)
                    got_it = True
                    if randint(1,100)<=this_event[1]:
                        got_it = False
                        for c in p.chars:
                            if (not got_it) and c.alive and not c.about_to_die:
                                for a in c.actions:
                                    if a.action_code==CA_PICK_LOCKS:
                                        if randint(1,100) <= a.chance():
                                            d.print_message('Pick Locks!',c.image,COL_LIGHT)
                                            got_it = True
                    if got_it:
                        self.wait(1500)
                        s.play(S_UNLOCKING)
                        self.wait(1000)
                        self.find_gold(randint(this_event[2],this_event[3]))
                    else:
                        self.wait(1500)
                        d.sound_queue.append(S_CANCEL)
                        d.message_queue.append(('But it is locked...',None,COL_LIGHT))
                elif this_event[0]==DE_GEMSCHEST:
                    d.message_queue.append(('You have found a chest!',I_GEMS_CHEST,COL_LIGHT))
                    got_it = True
                    if randint(1,100)<=this_event[1]:
                        got_it = False
                        for c in p.chars:
                            if (not got_it) and c.alive and not c.about_to_die:
                                for a in c.actions:
                                    if a.action_code==CA_PICK_LOCKS:
                                        if randint(1,100) <= a.chance():
                                            d.message_queue.append(('Pick Locks!',c.image,COL_LIGHT))
                                            got_it = True
                    if got_it:
                        self.wait(1500)
                        s.play(S_UNLOCKING)
                        self.wait(1000)
                        self.find_gems(randint(this_event[2],this_event[3]))
                    else:
                        self.wait(1500)
                        d.sound_queue.append(S_CANCEL)
                        d.message_queue.append(('But it is locked...',None,COL_LIGHT))
                elif this_event[0]==DE_NOFLY:
                    self.no_fly = True
                elif this_event[0]==DE_CANFLY:
                    self.no_fly = False
                self.started_pause_at = None
                self.started_respite_at = None
                self.event_no = self.event_no + 1
        # has player decided to flee?
        if self.party_fleeing and (self.start_fleeing_at is None):
            d.log('Party begins to flee')
            self.start_fleeing_at = self.elapsed_since_beginning
        # is any character ready to flee?
        if self.party_fleeing and self.n_fled < -1 + 0.5*(self.elapsed_since_beginning - self.start_fleeing_at) and p.n_surviving_chars()>0:
            t = p.random_back_target(None)
            if t is not None:
                self.n_fled = self.n_fled + t.flee()
            else:
                t = p.random_front_target(None)
                self.n_fled = self.n_fled + t.flee()
        if self.mobs_will_leave:
            # party are stealthy, mobs all buzz off
            if self.mobs_arrived < self.elapsed_since_beginning + 1:
                d.message_queue.append(('You sneak past the monsters!',None,COL_LIGHT))
                self.mobs_will_leave = False
                while e.n_surviving_mobs()>0:
                    e.weakest_target().remove()               
        else:
            for c in p.chars:
                if c.can_act() and e.n_surviving_front_mobs()>0 and (c.current_stance is None or c.current_stance==ST_BERSERK):
                    # front row characters get default melee attack
                    if c.front_row():
                        if c.melee_attack:
                            if one_in(floor(c.current_melee_period() / elapsed_since_last_process)):
                                target = e.random_front_target(c)
                                if target is not None:
                                    if c.melee_hit_roll(target):
                                        d.sound_queue.append(c.melee_hit_sound)
                                        damage = c.melee_damage_roll(target)
                                        armor = target.current_armor()
                                        if c.has_status_effect(EF_FIND_WEAKNESS):
                                            armor = max(0,armor-5)
                                        if damage > armor:
                                            d.log(c.name + ' hits ' + target.name)
                                            c.flash_image = I_PALE
                                            target.take_damage(damage-armor)
                                            if c.energy_for_hit:
                                                c.energy = min(c.max_energy,c.energy + 2)
                                        else:
                                            d.log(c.name + ' hits ' + target.name + ' for 0')
                                    else:
                                        d.sound_queue.append(c.melee_miss_sound)
                                        d.log(c.name + ' misses ' + target.name)
                    # back row characters get default missile attack
                    elif c.missile_attack:
                        if one_in(floor(c.current_missile_period() / elapsed_since_last_process)): 
                            target = e.random_front_target(c)
                            if target is not None:
                                if c.missile_hit_roll(target):
                                    d.sound_queue.append(c.missile_hit_sound)
                                    damage = c.missile_damage_roll(target)
                                    if damage > target.current_armor():
                                        d.log(c.name + ' shoots ' + target.name)
                                        c.flash_image = I_PALE
                                        target.take_damage(damage-target.current_armor())
                                        if c.energy_for_shot:
                                            c.energy = min(c.max_energy,c.energy + 2)
                                        if not target.front_row():
                                            target.has_been_shot = True
                                    else:
                                        d.log(c.name + ' shoots ' + target.name + ' for 0')
                                else:
                                    d.sound_queue.append(c.missile_miss_sound)
                                    d.log(c.name + ' shoots at ' + target.name + ' but misses')
            for m in e.mobs:
                # is it on fire or poisoned?
                if m.alive and not m.about_to_die:
                    if m.on_fire_until is not None and m.on_fire_until > self.elapsed_since_beginning and not m.fire_resistant:
                        if one_in(floor(m.fire_period / elapsed_since_last_process)):
                            m.take_damage(1)
                if m.alive and not m.about_to_die:
                    if m.poisoned_until is not None and m.poisoned_until > self.elapsed_since_beginning and not m.poison_resistant:
                        if one_in(floor(m.poison_period / elapsed_since_last_process)):
                            m.take_damage(1)
                    if m.regenerate and m.health < m.max_health:
                        if m.max_health >= 100:
                            if one_in(floor(0.15 / elapsed_since_last_process)):
                                m.health = m.health + 1
                        else:
                            if one_in(floor(0.3 / elapsed_since_last_process)):
                                m.health = m.health + 1
                    if m.split and m.health > 0.25*m.max_health and e.n_surviving_mobs() < 6:
                        if one_in(floor(5.0 / elapsed_since_last_process)):
                            new_mob = e.add_mob(m.code)
                            if new_mob is not None:
                                new_mob.xp_multiplier = m.xp_multiplier / 2
                                new_mob.slide_from = m.pos
                                d.log(m.name + ' splits!')
                                d.message_queue.append(('Splits!',m.image,COL_LIGHT))
                                new_mob.health = max(1,(2 * m.health) / 3);
                                m.health = max(1,(2 * m.health) / 3);
                                m.max_health = max(m.health, m.max_health - 10)
                                new_mob.max_health = max(new_mob.health, new_mob.max_health - 10)
                # is it rallied?
                rallied = (m.rallied_until > self.elapsed_since_beginning)
                # recover from confusion and stunning
                if m.alive and not m.about_to_die and m.confused and m.confused_until < self.elapsed_since_beginning:
                    m.confused = False
                    d.message_queue.append(('Recovers',m.image,COL_LIGHT))
                if m.alive and not m.about_to_die and m.stunned and m.stunned_until < self.elapsed_since_beginning:
                    m.stunned = False
                    d.message_queue.append(('Recovers',m.image,COL_LIGHT))
                 # can it act?
                if m.can_act():
                    has_acted = False
                    # a mob may be committed to moving
                    if m.move_to_front_at is not None and m.move_to_front_at<self.elapsed_since_beginning:
                        if e.last_free_front_pos() is not None:
                            m.slide_from = m.pos
                            m.slide_start = self.elapsed_since_beginning
                            m.slide_finish = self.elapsed_since_beginning + 0.1
                            m.pos = e.last_free_front_pos()
                            m.slide_to = m.pos
                        m.move_to_front_at = None
                    if m.move_to_back_at is not None and m.move_to_back_at<self.elapsed_since_beginning:
                        if e.last_free_back_pos() is not None:
                            m.slide_from = m.pos
                            m.slide_start = self.elapsed_since_beginning
                            m.slide_finish = self.elapsed_since_beginning + 0.1
                            m.pos = e.last_free_back_pos()
                            m.slide_to = m.pos
                        m.move_to_back_at = None
                    # if it is at the back, it must move forward if the front row is empty, unless there is a live back mob that may move forward later
                    if m.alive and not m.about_to_die and not m.front_row() and (m.move_to_front_at <= self.elapsed_since_beginning) and e.n_surviving_front_mobs()==0 and e.n_front_mobs_taking_up_space()==0:
                        ok = False
                        # massive kludge
                        for mm in e.mobs:
                            if mm.alive and not m.about_to_die and (m.code==MOB_HARPY or m.code==MOB_HILL_GIANT or m.code==MOB_YOUNG_RED_DRAGON or m.code==MOB_YOUNG_GREEN_DRAGON or m.code==MOB_YOUNG_BLUE_DRAGON or m.move_to_front_at >= self.elapsed_since_beginning):
                                ok = True
                                break
                        if not ok:
                            m.move_to_front_at = self.elapsed_since_beginning + 0.5
                    # a confused mob may wander around or even whack another mob
                    if m.alive and not m.about_to_die and m.confused_until >= self.elapsed_since_beginning:
                        if m.melee_attack and not m.stunned:
                            period = m.melee_period * 2.0
                            if one_in(floor(period / elapsed_since_last_process)):
                                target = None
                                if m.front_row() and e.n_surviving_front_mobs()>1:
                                    target = e.random_front_target(None)
                                    if target==m:
                                        target = None
                                if not m.front_row() and e.n_surviving_back_mobs()>1:
                                    target = e.random_back_target(None)
                                    if target==m:
                                        target = None
                                if target is not None:
                                    if randint(1,20) <= 2 * (5 + m.accuracy - target.current_evasion()):
                                        d.sound_queue.append(m.melee_hit_sound)
                                        damage = m.melee_damage_roll()
                                        armor = target.current_armor()
                                        if damage > armor:
                                            d.message_queue.append(('Hits ' + target.name,m.image,COL_LIGHT))
                                            d.log(m.name + ' hits ' + target.name)
                                            target.take_damage(max(0,damage-armor))
                                            m.flash_image = I_PALE
                                    else:
                                        d.sound_queue.append(m.melee_miss_sound)
                        if one_in(floor(4.0 / elapsed_since_last_process)):
                            if m.front_row() and e.n_surviving_front_mobs()<3:
                                to = e.random_free_front_pos()
                                if to is not None:
                                    m.slide_from = m.pos
                                    m.slide_start = self.elapsed_since_beginning
                                    m.slide_finish = self.elapsed_since_beginning + 0.1
                                    m.pos = to
                                    m.slide_to = m.pos
                            elif not m.front_row() and e.n_surviving_back_mobs()<3:
                                to = e.random_free_back_pos()
                                if to is not None:
                                    m.slide_from = m.pos
                                    m.slide_start = self.elapsed_since_beginning
                                    m.slide_finish = self.elapsed_since_beginning + 0.1
                                    m.pos = to
                                    m.slide_to = m.pos
                    # does the mob want to act?
                    if not m.blocked_by_gc():
                        for a in m.actions:
                            period = float(a.period())
                            if a.n_times_used < a.n_uses():
                                period = period / a.likelihood_increase_until_n_uses()
                            if rallied:
                                period = period * 0.7
                            if m.confused:
                                period = period * 3.0
                            if m.stunned:
                                period = period * 99
                            if a.ok() and one_in(floor(period / elapsed_since_last_process)):
                                if a.target_friend():
                                    target = choice(a.valid_targets())
                                elif a.target_enemy():
                                    target = None
                                    for i in range(10):
                                        test_target = p.random_target(m)
                                        if test_target in a.valid_targets():
                                            target = test_target
                                    if target is None:
                                        target = choice(a.valid_targets())
                                else:
                                    target = None
                                d.log(m.name + ' chooses ' + a.name())
                                a.trigger(target)
                                has_acted = True
                                break
                    if has_acted:
                        continue
                    # front row mobs get their default melee attack
                    if m.front_row() and m.melee_attack:
                        period = m.melee_period
                        if rallied:
                            period = period * 0.7
                        if m.confused:
                            period = period * 2.0
                        if m.stunned:
                            period = period * 99
                        if one_in(floor(period / elapsed_since_last_process)):
                            target = p.random_front_target(m)
                            if target is not None:
                                if m.melee_hit_roll(target):
                                    d.sound_queue.append(m.melee_hit_sound)
                                    damage = m.melee_damage_roll()
                                    armor = target.current_armor()
                                    if m.AP_melee:
                                        armor = 0
                                    if m.fire_melee and target.has_status_effect(EF_RESIST_FIRE):
                                        damage = damage / 2
                                    if damage > armor:
                                        d.log(m.name + ' hits ' + target.name)
                                        target.take_damage(max(0,damage-armor))
                                        m.flash_image = I_PALE
                                        # it might be a poison attack
                                        if m.poison_melee>0 and target.alive and not target.about_to_die and not target.has_status_effect(EF_RESIST_POISON):
                                            d.log(target.name + ' is poisoned')
                                            if target.has_status_effect(EF_POISON):
                                                poison_level = min(3,target.status_effects[EF_POISON].level + m.poison_melee)
                                            else:
                                                poison_level = m.poison_melee
                                            target.status_effects[EF_POISON] = Effect_parameters(10) 
                                            target.status_effects[EF_POISON].level = poison_level
                                    else:
                                        d.log(m.name + ' hits ' + target.name + ' for 0')
                                else:
                                    d.log(m.name + ' misses ' + target.name)
                                    d.sound_queue.append(m.melee_miss_sound)
                                continue
                    # rear row mobs get their default missile attack
                    if not m.front_row() and m.missile_attack:
                        period = m.missile_period
                        if rallied:
                            period = period * 0.7
                        if m.confused:
                            period = period * 3.0
                        if m.stunned:
                            period = period * 99
                        if one_in(floor(period / elapsed_since_last_process)):
                            target = p.random_front_target(m)
                            if target is not None:
                                if m.missile_hit_roll(target):
                                    d.sound_queue.append(m.missile_hit_sound)
                                    damage = m.missile_damage_roll()
                                    if damage > target.current_armor():
                                        d.log(m.name + ' hits ' + target.name)
                                        m.flash_image = I_PALE
                                        target.take_damage(max(0,damage-target.current_armor()))
                                    else:
                                        d.log(m.name + ' hits ' + target.name + ' for 0')
                                else:
                                    d.log(m.name + ' misses ' + target.name)
                                    d.sound_queue.append(m.missile_miss_sound)
                            continue

    def wait(self,ms):
        d.redraw(p,e,self)
        self.now = pygame.time.get_ticks()
        while pygame.time.get_ticks() < self.now + ms:
            pass
        self.now = pygame.time.get_ticks()
        self.since_last_process = self.now
        d.redraw(p,e,self)
        
# create game objects
p = Party()
w = World()
e = Enemies()
